├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── public ├── index.html ├── manifest.json └── robots.txt ├── src ├── App.css ├── App.js ├── App.test.js ├── components │ ├── Footer.js │ ├── Header.js │ └── RemoveBackground.js ├── features │ └── removeBackground.js ├── index.js ├── serviceWorker.js ├── setupTests.js └── store.js └── yarn.lock /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 igserdar 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 | 2 | ![Logo](https://i1.wp.com/fcdn.serdarbudak.com.tr/wp-content/uploads/2021/09/bgremove-min.png) 3 | 4 | 5 | # Image Background Removal Tool with React 6 | 7 | With the this Background Removal Tool you can simply remove the image background and download the result. 8 | 9 | ## Features 10 | 11 | - Fast 12 | - Simple Usage 13 | - Max Performance 14 | 15 | ## Demo 16 | 17 | Live [here](https://removebg.serdarbudak.com.tr/). 18 | 19 | 20 | ## License 21 | 22 | [MIT](https://choosealicense.com/licenses/mit/) 23 | 24 | 25 | ## Installation 26 | 27 | In the project directory, you can run: 28 | 29 | ```bash 30 | npm install 31 | 32 | ``` 33 | 34 | ## Run it on your computer 35 | 36 | 37 | In the project directory, you can run: 38 | ```bash 39 | npm start 40 | ``` 41 | 42 | 43 | ## Used Technologies 44 | 45 | React, Redux 46 | 47 | 48 | ## FeedBack 49 | 50 | If you have any feedback, please contact us at mail@serdarbudak.com.tr 51 | 52 | 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "remove-image-background", 3 | "author": "Serdar Budak", 4 | "version": "1.0.0", 5 | "private": true, 6 | "dependencies": { 7 | "@reduxjs/toolkit": "^1.5.1", 8 | "@testing-library/jest-dom": "^4.2.4", 9 | "@testing-library/react": "^9.3.2", 10 | "@testing-library/user-event": "^7.1.2", 11 | "blueimp-load-image": "^5.14.0", 12 | "react": "^17.0.2", 13 | "react-dom": "^17.0.2", 14 | "react-redux": "^7.2.3", 15 | "react-scripts": "4.0.3", 16 | "request": "^2.88.2" 17 | }, 18 | "scripts": { 19 | "start": "react-scripts start", 20 | "build": "react-scripts build", 21 | "test": "react-scripts test", 22 | "eject": "react-scripts eject" 23 | }, 24 | "eslintConfig": { 25 | "extends": "react-app" 26 | }, 27 | "browserslist": { 28 | "production": [ 29 | ">0.2%", 30 | "not dead", 31 | "not op_mini all" 32 | ], 33 | "development": [ 34 | "last 1 chrome version", 35 | "last 1 firefox version", 36 | "last 1 safari version" 37 | ] 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 16 | 17 | 18 | 19 | 23 | 24 | 33 | BG Removal Tool by Serdar Budak 34 | 35 | 36 | 37 | 38 |
39 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | 2 | @import url('https://fonts.googleapis.com/css?family=Raleway'); 3 | #upload { 4 | opacity: 0; 5 | } 6 | 7 | html { 8 | position: relative; 9 | min-height: 100%; 10 | } 11 | a { 12 | text-decoration: none; 13 | } 14 | #upload-label { 15 | position: absolute; 16 | top: 50%; 17 | left: 1rem; 18 | transform: translateY(-50%); 19 | } 20 | 21 | .image-area { 22 | border: 2px dashed rgba(255, 255, 255, 0.7); 23 | padding: 1rem; 24 | position: relative; 25 | } 26 | 27 | .image-area::before { 28 | content: 'Result'; 29 | color: #fff; 30 | font-weight: bold; 31 | text-transform: uppercase; 32 | position: absolute; 33 | top: 50%; 34 | left: 50%; 35 | transform: translate(-50%, -50%); 36 | font-size: 0.8rem; 37 | z-index: 1; 38 | } 39 | 40 | .image-area img { 41 | z-index: 2; 42 | position: relative; 43 | } 44 | 45 | /* 46 | * 47 | * ========================================== 48 | * FOR DEMO PURPOSES 49 | * ========================================== 50 | * 51 | */ 52 | body { 53 | font-family: Raleway; 54 | min-height: 100vh; 55 | background: linear-gradient(-45deg, #0065e9, #23a6d5, #23d5ab, #23a6d5, #23d5ab); 56 | background-size: 400% 400%; 57 | animation: gradient 15s ease infinite; 58 | 59 | } 60 | .footer { 61 | position: absolute; 62 | bottom: 0; 63 | width: 100%; 64 | height: 60px; 65 | line-height: 60px; 66 | background-color: #f5f5f5; 67 | text-align: center; 68 | } 69 | 70 | 71 | .text-muted{ 72 | padding: 5px; 73 | } 74 | 75 | /* Spinner Wrapper */ 76 | .loader { 77 | width: 100vw; 78 | height: 100vh; 79 | background: #fff; 80 | position: fixed; 81 | top: 0; 82 | left: 0; 83 | } 84 | 85 | .loader-inner { 86 | position: absolute; 87 | top: 50%; 88 | left: 50%; 89 | transform: translate(-50%, -50%); 90 | } 91 | 92 | 93 | /* Spinner */ 94 | .lds-roller { 95 | display: inline-block; 96 | position: relative; 97 | width: 64px; 98 | height: 64px; 99 | } 100 | .lds-roller div { 101 | animation: lds-roller 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; 102 | transform-origin: 32px 32px; 103 | } 104 | .lds-roller div:after { 105 | content: " "; 106 | display: block; 107 | position: absolute; 108 | align-items: center; 109 | width: 6px; 110 | height: 6px; 111 | border-radius: 50%; 112 | background: rgb(245, 243, 243); 113 | margin: -3px 0 0 -3px; 114 | } 115 | .lds-roller div:nth-child(1) { 116 | animation-delay: -0.036s; 117 | 118 | } 119 | .lds-roller div:nth-child(1):after { 120 | top: 50px; 121 | left: 50px; 122 | } 123 | .lds-roller div:nth-child(2) { 124 | animation-delay: -0.072s; 125 | } 126 | .lds-roller div:nth-child(2):after { 127 | top: 54px; 128 | left: 45px; 129 | } 130 | .lds-roller div:nth-child(3) { 131 | animation-delay: -0.108s; 132 | } 133 | .lds-roller div:nth-child(3):after { 134 | top: 57px; 135 | left: 39px; 136 | } 137 | .lds-roller div:nth-child(4) { 138 | animation-delay: -0.144s; 139 | } 140 | .lds-roller div:nth-child(4):after { 141 | top: 58px; 142 | left: 32px; 143 | } 144 | .lds-roller div:nth-child(5) { 145 | animation-delay: -0.18s; 146 | } 147 | .lds-roller div:nth-child(5):after { 148 | top: 57px; 149 | left: 25px; 150 | } 151 | .lds-roller div:nth-child(6) { 152 | animation-delay: -0.216s; 153 | } 154 | .lds-roller div:nth-child(6):after { 155 | top: 54px; 156 | left: 19px; 157 | } 158 | .lds-roller div:nth-child(7) { 159 | animation-delay: -0.252s; 160 | } 161 | .lds-roller div:nth-child(7):after { 162 | top: 50px; 163 | left: 14px; 164 | } 165 | .lds-roller div:nth-child(8) { 166 | animation-delay: -0.288s; 167 | } 168 | .lds-roller div:nth-child(8):after { 169 | top: 45px; 170 | left: 10px; 171 | } 172 | @keyframes lds-roller { 173 | 0% { 174 | transform: rotate(0deg); 175 | } 176 | 100% { 177 | transform: rotate(360deg); 178 | } 179 | } 180 | 181 | @keyframes gradient { 182 | 0% { 183 | background-position: 0% 50%; 184 | } 185 | 50% { 186 | background-position: 100% 50%; 187 | } 188 | 100% { 189 | background-position: 0% 50%; 190 | } 191 | } 192 | 193 | 194 | .down-button { 195 | border-radius: 50rem!important; 196 | margin-top: 20px; 197 | --bs-text-opacity: 1; 198 | color: #6c757d!important; 199 | } 200 | 201 | 202 | 203 | .remove-button{ 204 | border-radius: 50rem!important; 205 | } 206 | 207 | .heart { 208 | color: #ff4045; 209 | -webkit-animation: hearthing 1s ease infinite; 210 | animation: hearthing 1s ease infinite; 211 | } 212 | 213 | @-webkit-keyframes hearthing{ 214 | 0%{-webkit-transform:scale(.75);transform:scale(.75)} 215 | 20%{-webkit-transform:scale(1);transform:scale(1)} 216 | 40%{-webkit-transform:scale(.75);transform:scale(.75)} 217 | 60%{-webkit-transform:scale(1);transform:scale(1)} 218 | 80%{-webkit-transform:scale(.75);transform:scale(.75)}to{-webkit-transform:scale(.75);transform:scale(.75)}} 219 | @keyframes hearthing{ 220 | 0%{-webkit-transform:scale(.75);transform:scale(.75)} 221 | 20%{-webkit-transform:scale(1);transform:scale(1)} 222 | 40%{-webkit-transform:scale(.75);transform:scale(.75)} 223 | 60%{-webkit-transform:scale(1);transform:scale(1)} 224 | 80%{-webkit-transform:scale(.75);transform:scale(.75)}to{-webkit-transform:scale(.75);transform:scale(.75)}} 225 | 226 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "./App.css"; 3 | import Footer from "./components/Footer"; 4 | import Header from "./components/Header"; 5 | import RemoveBackground from "./components/RemoveBackground"; 6 | 7 | function App() { 8 | return ( 9 |
10 |
11 |
12 | 13 |
14 |
16 | ); 17 | } 18 | 19 | export default App; 20 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from '@testing-library/react'; 3 | import { Provider } from 'react-redux'; 4 | import { store } from './app/store'; 5 | import App from './App'; 6 | 7 | test('renders learn react link', () => { 8 | const { getByText } = render( 9 | 10 | 11 | 12 | ); 13 | 14 | expect(getByText(/learn/i)).toBeInTheDocument(); 15 | }); 16 | -------------------------------------------------------------------------------- /src/components/Footer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | export default class Footer extends Component { 4 | render() { 5 | return ( 6 |
7 | 12 |
13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/components/Header.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | export default class Header extends Component { 4 | render() { 5 | return ( 6 |
7 |
8 | serdar budak 14 | 15 |

Background Removal Tool

16 |

Fast, simple and great performance.

17 |
18 |
19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/components/RemoveBackground.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useSelector, useDispatch } from "react-redux"; 3 | import { setActionStatus } from "../features/removeBackground"; 4 | import loadImage from "blueimp-load-image"; 5 | 6 | export default function RemoveBackground() { 7 | const status = useSelector((state) => state.status.bgRemoved); 8 | const dispatch = useDispatch(); 9 | 10 | let blob = null; 11 | 12 | const [image, setImage] = useState(null); 13 | 14 | const imgUpload = (e) => { 15 | const img = e.target.files[0]; 16 | var input = document.getElementById("upload"); 17 | var infoArea = document.getElementById("upload-label"); 18 | var fileName = input.files[0].name; 19 | infoArea.textContent = "File name: " + fileName; 20 | 21 | setImage(img); 22 | }; 23 | 24 | const uploadImage = async () => { 25 | dispatch(setActionStatus(false)); 26 | 27 | const resizedImage = await loadImage(image, { 28 | // resize before sending to Remove.bg for performance 29 | maxWidth: 1500, 30 | maxHeight: 1500, 31 | canvas: true, 32 | }); 33 | 34 | resizedImage.image.toBlob(async function (inputBlob) { 35 | const formData = new FormData(); 36 | formData.append("image_file", inputBlob); 37 | 38 | const response = await fetch("https://api.remove.bg/v1.0/removebg", { 39 | method: "POST", 40 | headers: { 41 | "X-Api-Key": "Rn1PbjhV5MbZvVahFJ7jfzoh", 42 | }, 43 | body: formData, 44 | }); 45 | 46 | if (response.status === 200) { 47 | dispatch(setActionStatus(true)); 48 | } else { 49 | dispatch(setActionStatus(false)); 50 | } 51 | 52 | const outputBlob = await response.blob(); 53 | 54 | blob = URL.createObjectURL(outputBlob); 55 | const image = document.getElementById("imageResult"); 56 | const down = document.getElementById("down"); 57 | image.src = blob; 58 | down.href = blob; 59 | down.download = "save.png"; 60 | }); 61 | }; 62 | 63 | return ( 64 |
65 |
66 |
67 | 73 | 80 | 81 |
82 | 89 |
90 |
91 | 97 |
98 |
99 |
100 |

101 | The result will be rendered inside the box below. 102 |

103 |
104 | {status === false ? ( 105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 | ) : ( 116 | 122 | )}{" "} 123 |
124 | {status ? ( 125 | 126 | 130 | 131 | ) : null} 132 |
133 |
134 |
135 |
136 |
137 | ); 138 | } 139 | -------------------------------------------------------------------------------- /src/features/removeBackground.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | 3 | const initialState = { 4 | bgRemoved: null 5 | } 6 | 7 | const removeBackground = createSlice({ 8 | name: 'removeBg', 9 | initialState, 10 | reducers: { 11 | setActionStatus: (state, action ) => { 12 | return { ...state, bgRemoved: action.payload }; 13 | }, 14 | }, 15 | }); 16 | 17 | export const { setActionStatus } = removeBackground.actions; 18 | export default removeBackground.reducer; -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | import { store } from './store'; 5 | import { Provider } from 'react-redux'; 6 | import * as serviceWorker from './serviceWorker'; 7 | 8 | ReactDOM.render( 9 | 10 | 11 | 12 | 13 | , 14 | document.getElementById('root') 15 | ); 16 | 17 | // If you want your app to work offline and load faster, you can change 18 | // unregister() to register() below. Note this comes with some pitfalls. 19 | // Learn more about service workers: https://bit.ly/CRA-PWA 20 | serviceWorker.unregister(); 21 | -------------------------------------------------------------------------------- /src/serviceWorker.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://bit.ly/CRA-PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === 'localhost' || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === '[::1]' || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener('load', () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | 'This web app is being served cache-first by a service ' + 46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA' 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then((registration) => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === 'installed') { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | 'New content is available and will be used when all ' + 74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log('Content is cached for offline use.'); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch((error) => { 97 | console.error('Error during service worker registration:', error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl, { 104 | headers: { 'Service-Worker': 'script' }, 105 | }) 106 | .then((response) => { 107 | // Ensure service worker exists, and that we really are getting a JS file. 108 | const contentType = response.headers.get('content-type'); 109 | if ( 110 | response.status === 404 || 111 | (contentType != null && contentType.indexOf('javascript') === -1) 112 | ) { 113 | // No service worker found. Probably a different app. Reload the page. 114 | navigator.serviceWorker.ready.then((registration) => { 115 | registration.unregister().then(() => { 116 | window.location.reload(); 117 | }); 118 | }); 119 | } else { 120 | // Service worker found. Proceed as normal. 121 | registerValidSW(swUrl, config); 122 | } 123 | }) 124 | .catch(() => { 125 | console.log( 126 | 'No internet connection found. App is running in offline mode.' 127 | ); 128 | }); 129 | } 130 | 131 | export function unregister() { 132 | if ('serviceWorker' in navigator) { 133 | navigator.serviceWorker.ready.then((registration) => { 134 | registration.unregister(); 135 | }); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | -------------------------------------------------------------------------------- /src/store.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from '@reduxjs/toolkit'; 2 | import statusReducer from './features/removeBackground'; 3 | 4 | export const store = configureStore({ 5 | reducer: { 6 | status: statusReducer 7 | }, 8 | }); 9 | --------------------------------------------------------------------------------