├── .gitignore ├── LICENSE ├── README.md ├── example.png ├── lib ├── install.d.ts ├── install.js ├── main.d.ts ├── main.js ├── utils.d.ts └── utils.js ├── models └── config.yaml ├── package.json ├── pnpm-lock.yaml ├── src ├── install.ts ├── main.ts └── utils.ts ├── test.mjs ├── tiny.png └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | venv 2 | node_modules 3 | models/*.pth 4 | *.jpg 5 | *.png 6 | !tiny.png 7 | !example.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 [fullname] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Transparent Background (npm) 2 | 3 | [![](https://img.shields.io/npm/v/transparent-background)](https://www.npmjs.com/package/transparent-background) 4 | 5 | `transparent-background` lets you **easily remove backgrounds** from images 6 | 7 | Powered by [InSPyReNet (ACCV 2022)](https://github.com/plemeri/InSPyReNet), thanks to [@plemeri](https://github.com/plemeri/) for creating [transparent-background](https://github.com/plemeri/transparent-background) 8 | 9 | - **Python** must be installed system-wide 10 | - Works on **Linux, macOS and Windows** 11 | - Python is already pre-installed on **macOS** 12 | - If failing on **Windows**, you might have to install **Python 3.8** 13 | 14 | ```bash 15 | pnpm install transparent-background 16 | yarn add transparent-background 17 | npm install transparent-background 18 | ``` 19 | 20 | ## Example 21 | 22 | ![Before and after](https://github.com/makidoll/transparent-background-npm/blob/main/example.png) 23 | 24 | ```ts 25 | import * as fs from "fs/promises"; 26 | import { transparentBackground } from "transparent-background"; 27 | 28 | // const fs = require("fs/promises"); 29 | // const { transparentBackground } = require("transparent-background"); 30 | 31 | (async () => { 32 | const input = await fs.readFile("test-input.png"); 33 | 34 | const output = await transparentBackground(input, "png", { 35 | // uses a 1024x1024 model by default 36 | // enabling fast uses a 384x384 model instead 37 | fast: false, 38 | }); 39 | 40 | await fs.writeFile("test-output.png", output); 41 | })(); 42 | ``` 43 | -------------------------------------------------------------------------------- /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makinori/transparent-background-npm/833c4d05349401bfca56ce8c5e7167effd1b34d6/example.png -------------------------------------------------------------------------------- /lib/install.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /lib/install.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | var desc = Object.getOwnPropertyDescriptor(m, k); 5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { 6 | desc = { enumerable: true, get: function() { return m[k]; } }; 7 | } 8 | Object.defineProperty(o, k2, desc); 9 | }) : (function(o, m, k, k2) { 10 | if (k2 === undefined) k2 = k; 11 | o[k2] = m[k]; 12 | })); 13 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 14 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 15 | }) : function(o, v) { 16 | o["default"] = v; 17 | }); 18 | var __importStar = (this && this.__importStar) || function (mod) { 19 | if (mod && mod.__esModule) return mod; 20 | var result = {}; 21 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 22 | __setModuleDefault(result, mod); 23 | return result; 24 | }; 25 | var __importDefault = (this && this.__importDefault) || function (mod) { 26 | return (mod && mod.__esModule) ? mod : { "default": mod }; 27 | }; 28 | Object.defineProperty(exports, "__esModule", { value: true }); 29 | const execa_1 = __importDefault(require("execa")); 30 | const fs = __importStar(require("fs/promises")); 31 | const path = __importStar(require("path")); 32 | const main_1 = require("./main"); 33 | const utils_1 = require("./utils"); 34 | (async () => { 35 | const systemPythonPath = await (0, utils_1.findSystemPython)(); 36 | if (systemPythonPath == null) { 37 | throw new Error("Python not found. Please make sure its installed"); 38 | } 39 | // make venv dir 40 | // this fails for some reasons 41 | // try { 42 | // await fs.rm(venvDir, { recursive: true, force: true }); 43 | // } catch (error) {} 44 | await fs.mkdir(utils_1.venvDir, { recursive: true }); 45 | // setup venv dir 46 | await (0, execa_1.default)(systemPythonPath, ["-m", "venv", utils_1.venvDir], { 47 | stdout: "inherit", 48 | stderr: "inherit", 49 | }); 50 | // install package 51 | const venvPythonPath = path.resolve(utils_1.venvDir, utils_1.isWindows ? "Scripts/python.exe" : "bin/python"); 52 | await (0, execa_1.default)(venvPythonPath, ["-m", "pip", "install", "-U", "transparent-background"], // 1.2.12 53 | { 54 | stdout: "inherit", 55 | stderr: "inherit", 56 | env: { 57 | VIRTUAL_ENV: utils_1.venvDir, 58 | }, 59 | }); 60 | // modify python file 61 | for (const libVersion of ["lib", "lib64", "Lib"]) { 62 | const venvLibDir = path.resolve(utils_1.venvDir, libVersion); 63 | if (!(await (0, utils_1.exists)(venvLibDir))) 64 | continue; 65 | const venvLibFiles = await fs.readdir(venvLibDir); 66 | for (const pythonVersion of venvLibFiles) { 67 | const removerPyPath = path.resolve(venvLibDir, pythonVersion, "site-packages/transparent_background/Remover.py"); 68 | if (!(await (0, utils_1.exists)(removerPyPath))) 69 | continue; 70 | let removerPy = await fs.readFile(removerPyPath, "utf8"); 71 | removerPy = removerPy.replaceAll(/home_dir = [^]+?\n/gi, "home_dir = os.getenv('MODELS_DIR')\n"); 72 | await fs.writeFile(removerPyPath, removerPy); 73 | } 74 | } 75 | // lib/python3.11/site-packages/transparent_background/Remover.py 76 | // download models and test 77 | // if fails then installation will fail too 78 | // would be nice if we could move ~/.transparent-background inside here 79 | console.log("Downloading InSPyReNet models and testing them..."); 80 | const tinyPng = await fs.readFile(path.resolve(__dirname, "../tiny.png")); 81 | await (0, main_1.transparentBackground)(tinyPng, "png"); 82 | await (0, main_1.transparentBackground)(tinyPng, "png", { fast: true }); 83 | })(); 84 | -------------------------------------------------------------------------------- /lib/main.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | export declare function transparentBackground(file: Buffer, fileExt: string, options?: { 3 | fast?: boolean; 4 | }): Promise; 5 | -------------------------------------------------------------------------------- /lib/main.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | var desc = Object.getOwnPropertyDescriptor(m, k); 5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { 6 | desc = { enumerable: true, get: function() { return m[k]; } }; 7 | } 8 | Object.defineProperty(o, k2, desc); 9 | }) : (function(o, m, k, k2) { 10 | if (k2 === undefined) k2 = k; 11 | o[k2] = m[k]; 12 | })); 13 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 14 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 15 | }) : function(o, v) { 16 | o["default"] = v; 17 | }); 18 | var __importStar = (this && this.__importStar) || function (mod) { 19 | if (mod && mod.__esModule) return mod; 20 | var result = {}; 21 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 22 | __setModuleDefault(result, mod); 23 | return result; 24 | }; 25 | Object.defineProperty(exports, "__esModule", { value: true }); 26 | exports.transparentBackground = void 0; 27 | const execa = require("execa"); 28 | const fs = __importStar(require("fs/promises")); 29 | const path = __importStar(require("path")); 30 | const tmp = __importStar(require("tmp-promise")); 31 | const utils_1 = require("./utils"); 32 | async function transparentBackground(file, fileExt, options = {}) { 33 | if (!fileExt.startsWith(".")) 34 | fileExt = "." + fileExt; 35 | const inputFile = await tmp.file({ postfix: fileExt }); 36 | await fs.writeFile(inputFile.path, file); 37 | const outputDir = await tmp.dir({ 38 | unsafeCleanup: true, // recursive 39 | }); 40 | const { stdout, stderr } = await execa(utils_1.transparentBackgroundPath, [ 41 | ...(options.fast ? ["-m", "fast"] : []), 42 | "--source", 43 | inputFile.path, 44 | "--dest", 45 | outputDir.path, 46 | ], { 47 | reject: false, 48 | env: { 49 | VIRTUAL_ENV: utils_1.venvDir, 50 | MODELS_DIR: utils_1.modelsDir, 51 | }, 52 | }); 53 | await inputFile.cleanup(); 54 | const outputFilenames = await fs.readdir(outputDir.path); 55 | if (outputFilenames.length == 0) { 56 | await outputDir.cleanup(); 57 | console.log(stdout); 58 | console.log(stderr); 59 | throw new Error("No output files"); 60 | } 61 | const outputPath = path.resolve(outputDir.path, outputFilenames[0]); 62 | const outputBuffer = await fs.readFile(outputPath); 63 | outputDir.cleanup().catch(() => { 64 | fs.rm(outputDir.path, { recursive: true, force: true }).catch(() => { 65 | // dont wanna throw since the operation was successful 66 | console.log("Transparent background, failed to cleanup: " + outputDir.path); 67 | }); 68 | }); 69 | return outputBuffer; 70 | } 71 | exports.transparentBackground = transparentBackground; 72 | -------------------------------------------------------------------------------- /lib/utils.d.ts: -------------------------------------------------------------------------------- 1 | export declare const isWindows: boolean; 2 | export declare const venvDir: string; 3 | export declare const modelsDir: string; 4 | export declare const transparentBackgroundPath: string; 5 | export declare function findSystemPython(): Promise; 6 | export declare function exists(filePath: string): Promise; 7 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | var desc = Object.getOwnPropertyDescriptor(m, k); 5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { 6 | desc = { enumerable: true, get: function() { return m[k]; } }; 7 | } 8 | Object.defineProperty(o, k2, desc); 9 | }) : (function(o, m, k, k2) { 10 | if (k2 === undefined) k2 = k; 11 | o[k2] = m[k]; 12 | })); 13 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 14 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 15 | }) : function(o, v) { 16 | o["default"] = v; 17 | }); 18 | var __importStar = (this && this.__importStar) || function (mod) { 19 | if (mod && mod.__esModule) return mod; 20 | var result = {}; 21 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 22 | __setModuleDefault(result, mod); 23 | return result; 24 | }; 25 | var __importDefault = (this && this.__importDefault) || function (mod) { 26 | return (mod && mod.__esModule) ? mod : { "default": mod }; 27 | }; 28 | Object.defineProperty(exports, "__esModule", { value: true }); 29 | exports.exists = exports.findSystemPython = exports.transparentBackgroundPath = exports.modelsDir = exports.venvDir = exports.isWindows = void 0; 30 | const execa_1 = __importDefault(require("execa")); 31 | const fs = __importStar(require("fs/promises")); 32 | const os = __importStar(require("os")); 33 | const path = __importStar(require("path")); 34 | exports.isWindows = os.platform() == "win32"; 35 | exports.venvDir = path.resolve(__dirname, "../venv"); 36 | exports.modelsDir = path.resolve(__dirname, "../models"); 37 | exports.transparentBackgroundPath = path.resolve(exports.venvDir, exports.isWindows 38 | ? "Scripts/transparent-background.exe" 39 | : "bin/transparent-background"); 40 | async function findSystemPython() { 41 | const findPath = exports.isWindows ? "where" : "which"; 42 | const pythonNames = ["python3", "python"]; 43 | // windows has python3 in path but does microsoft store funny 44 | // which does stderr so lets try catch 45 | for (const pythonName of pythonNames) { 46 | try { 47 | const pythonPath = (await (0, execa_1.default)(findPath, [pythonName])).stdout.split("\n")[0]; // windows shows multiple lines 48 | if (pythonPath.includes("not found")) 49 | continue; // linux or mac 50 | if (pythonPath.includes("not find")) 51 | continue; // windows 52 | const pythonVersion = (await (0, execa_1.default)(pythonPath, ["--version"])) 53 | .stdout; 54 | if (!pythonVersion.includes("Python 3.")) 55 | continue; 56 | return pythonPath; 57 | } 58 | catch (error) { 59 | continue; 60 | } 61 | } 62 | return null; 63 | } 64 | exports.findSystemPython = findSystemPython; 65 | async function exists(filePath) { 66 | try { 67 | await fs.stat(filePath); 68 | return true; 69 | } 70 | catch (error) { 71 | return false; 72 | } 73 | } 74 | exports.exists = exists; 75 | -------------------------------------------------------------------------------- /models/config.yaml: -------------------------------------------------------------------------------- 1 | base: 2 | url: "https://drive.google.com/file/d/13oBl5MTVcWER3YU4fSxW3ATlVfueFQPY/view?usp=share_link" 3 | md5: "d692e3dd5fa1b9658949d452bebf1cda" 4 | ckpt_name: "ckpt_base.pth" 5 | http_proxy: NULL 6 | base_size: [1024, 1024] 7 | 8 | 9 | fast: 10 | url: "https://drive.google.com/file/d/1iRX-0MVbUjvAVns5MtVdng6CQlGOIo3m/view?usp=share_link" 11 | md5: "9efdbfbcc49b79ef0f7891c83d2fd52f" 12 | ckpt_name: "ckpt_fast.pth" 13 | http_proxy: NULL 14 | base_size: [384, 384] 15 | 16 | base-nightly: 17 | url: "https://drive.google.com/file/d/13YER0ri0RZkTdGQqWiwK795i39FrXNKL/view?usp=drive_link" 18 | md5: NULL 19 | ckpt_name: "ckpt_base_nightly.pth" 20 | http_proxy: NULL 21 | base_size: [1024, 1024] -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "transparent-background", 3 | "main": "lib/main.js", 4 | "version": "1.5.0", 5 | "license": "MIT", 6 | "repository": "https://github.com/makidoll/transparent-background-npm", 7 | "description": "transparent-background lets you easily remove backgrounds from images powered by InSPyReNet (ACCV 2022)", 8 | "scripts": { 9 | "build": "tsc", 10 | "install": "node lib/install.js" 11 | }, 12 | "dependencies": { 13 | "execa": "^5.1.1", 14 | "tmp-promise": "^3.0.3" 15 | }, 16 | "devDependencies": { 17 | "@types/node": "^20.11.21", 18 | "typescript": "^5.3.3" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '6.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | dependencies: 8 | execa: 9 | specifier: ^5.1.1 10 | version: 5.1.1 11 | tmp-promise: 12 | specifier: ^3.0.3 13 | version: 3.0.3 14 | 15 | devDependencies: 16 | '@types/node': 17 | specifier: ^20.11.21 18 | version: 20.11.21 19 | typescript: 20 | specifier: ^5.3.3 21 | version: 5.3.3 22 | 23 | packages: 24 | 25 | /@isaacs/cliui@8.0.2: 26 | resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} 27 | engines: {node: '>=12'} 28 | dependencies: 29 | string-width: 5.1.2 30 | string-width-cjs: /string-width@4.2.3 31 | strip-ansi: 7.1.0 32 | strip-ansi-cjs: /strip-ansi@6.0.1 33 | wrap-ansi: 8.1.0 34 | wrap-ansi-cjs: /wrap-ansi@7.0.0 35 | dev: false 36 | 37 | /@pkgjs/parseargs@0.11.0: 38 | resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} 39 | engines: {node: '>=14'} 40 | requiresBuild: true 41 | dev: false 42 | optional: true 43 | 44 | /@types/node@20.11.21: 45 | resolution: {integrity: sha512-/ySDLGscFPNasfqStUuWWPfL78jompfIoVzLJPVVAHBh6rpG68+pI2Gk+fNLeI8/f1yPYL4s46EleVIc20F1Ow==} 46 | dependencies: 47 | undici-types: 5.26.5 48 | dev: true 49 | 50 | /ansi-regex@5.0.1: 51 | resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 52 | engines: {node: '>=8'} 53 | dev: false 54 | 55 | /ansi-regex@6.0.1: 56 | resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} 57 | engines: {node: '>=12'} 58 | dev: false 59 | 60 | /ansi-styles@4.3.0: 61 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 62 | engines: {node: '>=8'} 63 | dependencies: 64 | color-convert: 2.0.1 65 | dev: false 66 | 67 | /ansi-styles@6.2.1: 68 | resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} 69 | engines: {node: '>=12'} 70 | dev: false 71 | 72 | /balanced-match@1.0.2: 73 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 74 | dev: false 75 | 76 | /brace-expansion@2.0.1: 77 | resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} 78 | dependencies: 79 | balanced-match: 1.0.2 80 | dev: false 81 | 82 | /color-convert@2.0.1: 83 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 84 | engines: {node: '>=7.0.0'} 85 | dependencies: 86 | color-name: 1.1.4 87 | dev: false 88 | 89 | /color-name@1.1.4: 90 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 91 | dev: false 92 | 93 | /cross-spawn@7.0.3: 94 | resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} 95 | engines: {node: '>= 8'} 96 | dependencies: 97 | path-key: 3.1.1 98 | shebang-command: 2.0.0 99 | which: 2.0.2 100 | dev: false 101 | 102 | /eastasianwidth@0.2.0: 103 | resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} 104 | dev: false 105 | 106 | /emoji-regex@8.0.0: 107 | resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} 108 | dev: false 109 | 110 | /emoji-regex@9.2.2: 111 | resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} 112 | dev: false 113 | 114 | /execa@5.1.1: 115 | resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} 116 | engines: {node: '>=10'} 117 | dependencies: 118 | cross-spawn: 7.0.3 119 | get-stream: 6.0.1 120 | human-signals: 2.1.0 121 | is-stream: 2.0.1 122 | merge-stream: 2.0.0 123 | npm-run-path: 4.0.1 124 | onetime: 5.1.2 125 | signal-exit: 3.0.7 126 | strip-final-newline: 2.0.0 127 | dev: false 128 | 129 | /foreground-child@3.1.1: 130 | resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} 131 | engines: {node: '>=14'} 132 | dependencies: 133 | cross-spawn: 7.0.3 134 | signal-exit: 4.1.0 135 | dev: false 136 | 137 | /get-stream@6.0.1: 138 | resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} 139 | engines: {node: '>=10'} 140 | dev: false 141 | 142 | /glob@10.3.10: 143 | resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} 144 | engines: {node: '>=16 || 14 >=14.17'} 145 | hasBin: true 146 | dependencies: 147 | foreground-child: 3.1.1 148 | jackspeak: 2.3.6 149 | minimatch: 9.0.3 150 | minipass: 7.0.4 151 | path-scurry: 1.10.1 152 | dev: false 153 | 154 | /human-signals@2.1.0: 155 | resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} 156 | engines: {node: '>=10.17.0'} 157 | dev: false 158 | 159 | /is-fullwidth-code-point@3.0.0: 160 | resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} 161 | engines: {node: '>=8'} 162 | dev: false 163 | 164 | /is-stream@2.0.1: 165 | resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} 166 | engines: {node: '>=8'} 167 | dev: false 168 | 169 | /isexe@2.0.0: 170 | resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 171 | dev: false 172 | 173 | /jackspeak@2.3.6: 174 | resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} 175 | engines: {node: '>=14'} 176 | dependencies: 177 | '@isaacs/cliui': 8.0.2 178 | optionalDependencies: 179 | '@pkgjs/parseargs': 0.11.0 180 | dev: false 181 | 182 | /lru-cache@10.2.0: 183 | resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==} 184 | engines: {node: 14 || >=16.14} 185 | dev: false 186 | 187 | /merge-stream@2.0.0: 188 | resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} 189 | dev: false 190 | 191 | /mimic-fn@2.1.0: 192 | resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} 193 | engines: {node: '>=6'} 194 | dev: false 195 | 196 | /minimatch@9.0.3: 197 | resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} 198 | engines: {node: '>=16 || 14 >=14.17'} 199 | dependencies: 200 | brace-expansion: 2.0.1 201 | dev: false 202 | 203 | /minipass@7.0.4: 204 | resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==} 205 | engines: {node: '>=16 || 14 >=14.17'} 206 | dev: false 207 | 208 | /npm-run-path@4.0.1: 209 | resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} 210 | engines: {node: '>=8'} 211 | dependencies: 212 | path-key: 3.1.1 213 | dev: false 214 | 215 | /onetime@5.1.2: 216 | resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} 217 | engines: {node: '>=6'} 218 | dependencies: 219 | mimic-fn: 2.1.0 220 | dev: false 221 | 222 | /path-key@3.1.1: 223 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 224 | engines: {node: '>=8'} 225 | dev: false 226 | 227 | /path-scurry@1.10.1: 228 | resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==} 229 | engines: {node: '>=16 || 14 >=14.17'} 230 | dependencies: 231 | lru-cache: 10.2.0 232 | minipass: 7.0.4 233 | dev: false 234 | 235 | /rimraf@5.0.5: 236 | resolution: {integrity: sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==} 237 | engines: {node: '>=14'} 238 | hasBin: true 239 | dependencies: 240 | glob: 10.3.10 241 | dev: false 242 | 243 | /shebang-command@2.0.0: 244 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 245 | engines: {node: '>=8'} 246 | dependencies: 247 | shebang-regex: 3.0.0 248 | dev: false 249 | 250 | /shebang-regex@3.0.0: 251 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 252 | engines: {node: '>=8'} 253 | dev: false 254 | 255 | /signal-exit@3.0.7: 256 | resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} 257 | dev: false 258 | 259 | /signal-exit@4.1.0: 260 | resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} 261 | engines: {node: '>=14'} 262 | dev: false 263 | 264 | /string-width@4.2.3: 265 | resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} 266 | engines: {node: '>=8'} 267 | dependencies: 268 | emoji-regex: 8.0.0 269 | is-fullwidth-code-point: 3.0.0 270 | strip-ansi: 6.0.1 271 | dev: false 272 | 273 | /string-width@5.1.2: 274 | resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} 275 | engines: {node: '>=12'} 276 | dependencies: 277 | eastasianwidth: 0.2.0 278 | emoji-regex: 9.2.2 279 | strip-ansi: 7.1.0 280 | dev: false 281 | 282 | /strip-ansi@6.0.1: 283 | resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 284 | engines: {node: '>=8'} 285 | dependencies: 286 | ansi-regex: 5.0.1 287 | dev: false 288 | 289 | /strip-ansi@7.1.0: 290 | resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} 291 | engines: {node: '>=12'} 292 | dependencies: 293 | ansi-regex: 6.0.1 294 | dev: false 295 | 296 | /strip-final-newline@2.0.0: 297 | resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} 298 | engines: {node: '>=6'} 299 | dev: false 300 | 301 | /tmp-promise@3.0.3: 302 | resolution: {integrity: sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==} 303 | dependencies: 304 | tmp: 0.2.2 305 | dev: false 306 | 307 | /tmp@0.2.2: 308 | resolution: {integrity: sha512-ETcvHhaIc9J2MDEAH6N67j9bvBvu/3Gb764qaGhwtFvjtvhegqoqSpofgeyq1Sc24mW5pdyUDs9HP5j3ehkxRw==} 309 | engines: {node: '>=14'} 310 | dependencies: 311 | rimraf: 5.0.5 312 | dev: false 313 | 314 | /typescript@5.3.3: 315 | resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} 316 | engines: {node: '>=14.17'} 317 | hasBin: true 318 | dev: true 319 | 320 | /undici-types@5.26.5: 321 | resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} 322 | dev: true 323 | 324 | /which@2.0.2: 325 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 326 | engines: {node: '>= 8'} 327 | hasBin: true 328 | dependencies: 329 | isexe: 2.0.0 330 | dev: false 331 | 332 | /wrap-ansi@7.0.0: 333 | resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} 334 | engines: {node: '>=10'} 335 | dependencies: 336 | ansi-styles: 4.3.0 337 | string-width: 4.2.3 338 | strip-ansi: 6.0.1 339 | dev: false 340 | 341 | /wrap-ansi@8.1.0: 342 | resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} 343 | engines: {node: '>=12'} 344 | dependencies: 345 | ansi-styles: 6.2.1 346 | string-width: 5.1.2 347 | strip-ansi: 7.1.0 348 | dev: false 349 | -------------------------------------------------------------------------------- /src/install.ts: -------------------------------------------------------------------------------- 1 | import execa from "execa"; 2 | import * as fs from "fs/promises"; 3 | import * as path from "path"; 4 | import { transparentBackground } from "./main"; 5 | import { exists, findSystemPython, isWindows, venvDir } from "./utils"; 6 | 7 | (async () => { 8 | const systemPythonPath = await findSystemPython(); 9 | if (systemPythonPath == null) { 10 | throw new Error("Python not found. Please make sure its installed"); 11 | } 12 | 13 | // make venv dir 14 | 15 | // this fails for some reasons 16 | // try { 17 | // await fs.rm(venvDir, { recursive: true, force: true }); 18 | // } catch (error) {} 19 | 20 | await fs.mkdir(venvDir, { recursive: true }); 21 | 22 | // setup venv dir 23 | 24 | await execa(systemPythonPath, ["-m", "venv", venvDir], { 25 | stdout: "inherit", 26 | stderr: "inherit", 27 | }); 28 | 29 | // install package 30 | 31 | const venvPythonPath = path.resolve( 32 | venvDir, 33 | isWindows ? "Scripts/python.exe" : "bin/python", 34 | ); 35 | 36 | await execa( 37 | venvPythonPath, 38 | ["-m", "pip", "install", "-U", "transparent-background"], // 1.2.12 39 | { 40 | stdout: "inherit", 41 | stderr: "inherit", 42 | env: { 43 | VIRTUAL_ENV: venvDir, 44 | }, 45 | }, 46 | ); 47 | 48 | // modify python file 49 | 50 | for (const libVersion of ["lib", "lib64", "Lib"]) { 51 | const venvLibDir = path.resolve(venvDir, libVersion); 52 | if (!(await exists(venvLibDir))) continue; 53 | 54 | const venvLibFiles = await fs.readdir(venvLibDir); 55 | 56 | for (const pythonVersion of venvLibFiles) { 57 | const removerPyPath = path.resolve( 58 | venvLibDir, 59 | pythonVersion, 60 | "site-packages/transparent_background/Remover.py", 61 | ); 62 | 63 | if (!(await exists(removerPyPath))) continue; 64 | 65 | let removerPy = await fs.readFile(removerPyPath, "utf8"); 66 | 67 | removerPy = removerPy.replaceAll( 68 | /home_dir = [^]+?\n/gi, 69 | "home_dir = os.getenv('MODELS_DIR')\n", 70 | ); 71 | 72 | await fs.writeFile(removerPyPath, removerPy); 73 | } 74 | } 75 | 76 | // lib/python3.11/site-packages/transparent_background/Remover.py 77 | 78 | // download models and test 79 | // if fails then installation will fail too 80 | // would be nice if we could move ~/.transparent-background inside here 81 | 82 | console.log("Downloading InSPyReNet models and testing them..."); 83 | 84 | const tinyPng = await fs.readFile(path.resolve(__dirname, "../tiny.png")); 85 | 86 | await transparentBackground(tinyPng, "png"); 87 | await transparentBackground(tinyPng, "png", { fast: true }); 88 | })(); 89 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import execa = require("execa"); 2 | import * as fs from "fs/promises"; 3 | import * as path from "path"; 4 | import * as tmp from "tmp-promise"; 5 | import { modelsDir, transparentBackgroundPath, venvDir } from "./utils"; 6 | 7 | export async function transparentBackground( 8 | file: Buffer, 9 | fileExt: string, 10 | options: { fast?: boolean } = {}, 11 | ) { 12 | if (!fileExt.startsWith(".")) fileExt = "." + fileExt; 13 | const inputFile = await tmp.file({ postfix: fileExt }); 14 | 15 | await fs.writeFile(inputFile.path, file); 16 | 17 | const outputDir = await tmp.dir({ 18 | unsafeCleanup: true, // recursive 19 | }); 20 | 21 | const { stdout, stderr } = await execa( 22 | transparentBackgroundPath, 23 | [ 24 | ...(options.fast ? ["-m", "fast"] : []), 25 | "--source", 26 | inputFile.path, 27 | "--dest", 28 | outputDir.path, 29 | ], 30 | { 31 | reject: false, 32 | env: { 33 | VIRTUAL_ENV: venvDir, 34 | MODELS_DIR: modelsDir, 35 | }, 36 | }, 37 | ); 38 | 39 | await inputFile.cleanup(); 40 | 41 | const outputFilenames = await fs.readdir(outputDir.path); 42 | if (outputFilenames.length == 0) { 43 | await outputDir.cleanup(); 44 | 45 | console.log(stdout); 46 | console.log(stderr); 47 | 48 | throw new Error("No output files"); 49 | } 50 | 51 | const outputPath = path.resolve(outputDir.path, outputFilenames[0]); 52 | const outputBuffer = await fs.readFile(outputPath); 53 | 54 | outputDir.cleanup().catch(() => { 55 | fs.rm(outputDir.path, { recursive: true, force: true }).catch(() => { 56 | // dont wanna throw since the operation was successful 57 | console.log( 58 | "Transparent background, failed to cleanup: " + outputDir.path, 59 | ); 60 | }); 61 | }); 62 | 63 | return outputBuffer; 64 | } 65 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import execa from "execa"; 2 | import * as fs from "fs/promises"; 3 | import * as os from "os"; 4 | import * as path from "path"; 5 | 6 | export const isWindows = os.platform() == "win32"; 7 | 8 | export const venvDir = path.resolve(__dirname, "../venv"); 9 | export const modelsDir = path.resolve(__dirname, "../models"); 10 | 11 | export const transparentBackgroundPath = path.resolve( 12 | venvDir, 13 | isWindows 14 | ? "Scripts/transparent-background.exe" 15 | : "bin/transparent-background", 16 | ); 17 | 18 | export async function findSystemPython() { 19 | const findPath = isWindows ? "where" : "which"; 20 | const pythonNames = ["python3", "python"]; 21 | 22 | // windows has python3 in path but does microsoft store funny 23 | // which does stderr so lets try catch 24 | 25 | for (const pythonName of pythonNames) { 26 | try { 27 | const pythonPath = ( 28 | await execa(findPath, [pythonName]) 29 | ).stdout.split("\n")[0]; // windows shows multiple lines 30 | 31 | if (pythonPath.includes("not found")) continue; // linux or mac 32 | if (pythonPath.includes("not find")) continue; // windows 33 | 34 | const pythonVersion = (await execa(pythonPath, ["--version"])) 35 | .stdout; 36 | 37 | if (!pythonVersion.includes("Python 3.")) continue; 38 | 39 | return pythonPath; 40 | } catch (error) { 41 | continue; 42 | } 43 | } 44 | 45 | return null; 46 | } 47 | 48 | export async function exists(filePath: string) { 49 | try { 50 | await fs.stat(filePath); 51 | return true; 52 | } catch (error) { 53 | return false; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test.mjs: -------------------------------------------------------------------------------- 1 | import * as fs from "fs/promises"; 2 | import { transparentBackground } from "./lib/main.js"; 3 | 4 | // const fs = require("fs/promises"); 5 | // const { transparentBackground } = require("transparent-background"); 6 | 7 | (async () => { 8 | const input = await fs.readFile("test-input.jpg"); 9 | 10 | const output = await transparentBackground(input, "jpg", { 11 | // uses a 1024x1024 model by default 12 | // enabling fast uses a 384x384 model instead 13 | fast: false, 14 | }); 15 | 16 | await fs.writeFile("test-output.png", output); 17 | })(); 18 | -------------------------------------------------------------------------------- /tiny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/makinori/transparent-background-npm/833c4d05349401bfca56ce8c5e7167effd1b34d6/tiny.png -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["es2022"], 4 | "module": "commonjs", 5 | "target": "es2022", 6 | "outDir": "lib", 7 | "declaration": true, 8 | "esModuleInterop": true 9 | }, 10 | "include": ["src/**/*"] 11 | } 12 | --------------------------------------------------------------------------------