This commit is contained in:
wq.chu 2021-11-15 15:22:34 +08:00
commit 4e027f81e6
44 changed files with 14458 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.DS_Store
**/__pycache__

201
LICENSE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

19
README.md Normal file
View File

@ -0,0 +1,19 @@
# Lama-cleaner: Image inpainting tool powered by [LaMa](https://github.com/saic-mdal/lama)
This project is mainly used for selfhosting LaMa model, some interaction improvements may be added later.
## Quick Start
- Install requirements: `pip3 install -r requirements.txt`
- Start server: `python3 main.py --device=cuda --port=8080`
## Development
### Fronted
Frontend code are modified from [cleanup.pictures](https://github.com/initml/cleanup.pictures),
You can experience their great online services [here](https://cleanup.pictures/).
- Install dependencies:`cd lama_cleaner/app/ && yarn`
- Start development server: `yarn dev`
- Build: `yarn build`

1
lama_cleaner/app/.env Normal file
View File

@ -0,0 +1 @@
REACT_APP_INPAINTING_URL=""

View File

@ -0,0 +1,50 @@
{
"extends": [
"airbnb",
"airbnb/hooks",
"plugin:@typescript-eslint/recommended",
"prettier",
"plugin:prettier/recommended"
],
"plugins": ["@typescript-eslint", "react", "react-hooks", "prettier"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 2018,
"sourceType": "module",
"project": "./tsconfig.json"
},
"rules": {
"import/no-unresolved": 0,
"react/jsx-no-bind": "off",
"react/jsx-filename-extension": [
1,
{
"extensions": [".ts", ".tsx"]
}
],
"prettier/prettier": [
"error",
{
"singleQuote": true,
"arrowParens": "avoid",
"endOfLine": "auto"
}
],
"consistent-return": "off",
"no-use-before-define": "off",
"import/extensions": "off",
"react/prop-types": 0,
"react/require-default-props": "off",
"no-shadow": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-shadow": ["error"],
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
}
}

27
lama_cleaner/app/.gitignore vendored Normal file
View File

@ -0,0 +1,27 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Tailwind processed CSS
index.css
.firebase

View File

@ -0,0 +1,6 @@
{
"singleQuote": true,
"semi": false,
"trailingComma": "es5",
"arrowParens": "avoid"
}

201
lama_cleaner/app/LICENSE Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,20 @@
{
"files": {
"main.css": "/static/css/main.2d68bf69.chunk.css",
"main.js": "/static/js/main.56b43b57.chunk.js",
"main.js.map": "/static/js/main.56b43b57.chunk.js.map",
"runtime-main.js": "/static/js/runtime-main.5e86ac81.js",
"runtime-main.js.map": "/static/js/runtime-main.5e86ac81.js.map",
"static/js/2.1884f63d.chunk.js": "/static/js/2.1884f63d.chunk.js",
"static/js/2.1884f63d.chunk.js.map": "/static/js/2.1884f63d.chunk.js.map",
"index.html": "/index.html",
"static/css/main.2d68bf69.chunk.css.map": "/static/css/main.2d68bf69.chunk.css.map",
"static/js/2.1884f63d.chunk.js.LICENSE.txt": "/static/js/2.1884f63d.chunk.js.LICENSE.txt"
},
"entrypoints": [
"static/js/runtime-main.5e86ac81.js",
"static/js/2.1884f63d.chunk.js",
"static/css/main.2d68bf69.chunk.css",
"static/js/main.56b43b57.chunk.js"
]
}

View File

@ -0,0 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" type="image/svg+xml" href="/favicon.svg"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=0"/><meta name="theme-color" content="#ffffff"/><title>lama-cleaner - Image inpainting powered by LaMa</title><link href="/static/css/main.2d68bf69.chunk.css" rel="stylesheet"></head><body class="h-screen"><noscript>You need to enable JavaScript to run this app.</noscript><div id="root" class="h-full"></div><script>"localhost"===location.hostname&&(self.FIREBASE_APPCHECK_DEBUG_TOKEN=!0)</script><script>!function(e){function r(r){for(var n,l,a=r[0],f=r[1],i=r[2],p=0,s=[];p<a.length;p++)l=a[p],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in f)Object.prototype.hasOwnProperty.call(f,n)&&(e[n]=f[n]);for(c&&c(r);s.length;)s.shift()();return u.push.apply(u,i||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,a=1;a<t.length;a++){var f=t[a];0!==o[f]&&(n=!1)}n&&(u.splice(r--,1),e=l(l.s=t[0]))}return e}var n={},o={1:0},u=[];function l(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,l),t.l=!0,t.exports}l.m=e,l.c=n,l.d=function(e,r,t){l.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,r){if(1&r&&(e=l(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(l.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)l.d(t,n,function(r){return e[r]}.bind(null,n));return t},l.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(r,"a",r),r},l.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},l.p="/";var a=this["webpackJsonplama-cleaner"]=this["webpackJsonplama-cleaner"]||[],f=a.push.bind(a);a.push=r,a=a.slice();for(var i=0;i<a.length;i++)r(a[i]);var c=f;t()}([])</script><script src="/static/js/2.1884f63d.chunk.js"></script><script src="/static/js/main.56b43b57.chunk.js"></script></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,41 @@
/*
object-assign
(c) Sindre Sorhus
@license MIT
*/
/** @license React v0.20.2
* scheduler.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v17.0.2
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v17.0.2
* react-jsx-runtime.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/** @license React v17.0.2
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,2 @@
!function(e){function r(r){for(var n,l,a=r[0],f=r[1],i=r[2],p=0,s=[];p<a.length;p++)l=a[p],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in f)Object.prototype.hasOwnProperty.call(f,n)&&(e[n]=f[n]);for(c&&c(r);s.length;)s.shift()();return u.push.apply(u,i||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,a=1;a<t.length;a++){var f=t[a];0!==o[f]&&(n=!1)}n&&(u.splice(r--,1),e=l(l.s=t[0]))}return e}var n={},o={1:0},u=[];function l(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,l),t.l=!0,t.exports}l.m=e,l.c=n,l.d=function(e,r,t){l.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},l.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,r){if(1&r&&(e=l(e)),8&r)return e;if(4&r&&"object"===typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(l.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)l.d(t,n,function(r){return e[r]}.bind(null,n));return t},l.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(r,"a",r),r},l.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},l.p="/";var a=this["webpackJsonplama-cleaner"]=this["webpackJsonplama-cleaner"]||[],f=a.push.bind(a);a.push=r,a=a.slice();for(var i=0;i<a.length;i++)r(a[i]);var c=f;t()}([]);
//# sourceMappingURL=runtime-main.5e86ac81.js.map

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,65 @@
{
"name": "lama-cleaner",
"version": "0.1.0",
"private": true,
"proxy": "http://localhost:8080",
"dependencies": {
"@heroicons/react": "^1.0.4",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^12.1.2",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.0.2",
"@types/node": "^16.11.1",
"@types/react": "^17.0.30",
"@types/react-dom": "^17.0.9",
"autoprefixer": "10.x",
"cross-env": "7.x",
"delay-cli": "^1.1.0",
"npm-run-all": "4.x",
"postcss": "8.x",
"postcss-cli": "8.x",
"postcss-preset-env": "6.x",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-scripts": "4.0.3",
"react-use": "^17.3.1",
"tailwindcss": "2.x",
"typescript": "4.x"
},
"scripts": {
"dev": "run-p watch:css react-scripts:start",
"build": "run-s build:css react-scripts:build",
"test": "react-scripts test",
"eject": "react-scripts eject",
"build:css": "cross-env TAILWIND_MODE=build NODE_ENV=production postcss src/styles/tailwind.css -o src/styles/index.css",
"watch:css": "cross-env TAILWIND_MODE=watch NODE_ENV=development postcss src/styles/tailwind.css -o src/styles/index.css --watch",
"react-scripts:start": "delay 5 && react-scripts start",
"react-scripts:build": "react-scripts build"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^5.1.0",
"eslint-config-airbnb": "^18.2.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.25.2",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.26.1",
"eslint-plugin-react-hooks": "^4.2.0",
"prettier": "^2.4.1"
}
}

View File

@ -0,0 +1,6 @@
module.exports = {
plugins: [
require('tailwindcss'),
require('postcss-preset-env'),
],
};

View File

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" type="image/svg+xml" href="%PUBLIC_URL%/favicon.svg" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=0"
/>
<meta name="theme-color" content="#ffffff" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>lama-cleaner - Image inpainting powered by LaMa</title>
</head>
<body class="h-screen">
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root" class="h-full"></div>
<script>
if (location.hostname === 'localhost') {
self.FIREBASE_APPCHECK_DEBUG_TOKEN = true
}
</script>
</body>
</html>

View File

@ -0,0 +1,81 @@
import { ArrowLeftIcon } from '@heroicons/react/outline'
import React, { useState } from 'react'
import { useWindowSize } from 'react-use'
import Button from './components/Button'
import FileSelect from './components/FileSelect'
import Editor from './Editor'
import { resizeImageFile } from './utils'
function App() {
const [file, setFile] = useState<File>()
const windowSize = useWindowSize()
return (
<div className="h-full full-visible-h-safari flex flex-col">
<header className="relative z-10 flex px-5 pt-3 justify-center sm:justify-between items-center sm:items-start">
{file ? (
<Button
icon={<ArrowLeftIcon className="w-6 h-6" />}
onClick={() => {
setFile(undefined)
}}
>
{windowSize.width > 640 ? 'Start new' : undefined}
</Button>
) : (
<></>
)}
</header>
<main
className={[
'h-full flex flex-1 flex-col sm:items-center sm:justify-center overflow-hidden',
// file ? 'items-center justify-center' : '', // center on mobile
'items-center justify-center',
'pb-20',
].join(' ')}
>
{file ? (
<Editor file={file} />
) : (
<>
<div
className={[
'flex flex-col sm:flex-row items-center',
'space-y-5 sm:space-y-0 sm:space-x-6 p-5 pt-0 pb-10',
].join(' ')}
>
<div className="max-w-xl flex flex-col items-center sm:items-start p-0 m-0 space-y-5">
<h1 className="text-center sm:text-left text-xl sm:text-3xl">
Image inpainting powered by 🦙
<u>
<a href="https://github.com/saic-mdal/lama">LaMa</a>
</u>
</h1>
</div>
</div>
<div
className="h-20 sm:h-52 px-4 w-full"
style={{ maxWidth: '800px' }}
>
<FileSelect
onSelection={async f => {
const {
file: resizedFile,
resized,
originalWidth,
originalHeight,
} = await resizeImageFile(f, 1024)
setFile(resizedFile)
}}
/>
</div>
</>
)}
</main>
</div>
)
}
export default App

View File

@ -0,0 +1,409 @@
import { DownloadIcon, EyeIcon } from '@heroicons/react/outline'
import React, { useCallback, useEffect, useState } from 'react'
import { useWindowSize } from 'react-use'
import inpaint from './adapters/inpainting'
import Button from './components/Button'
import Slider from './components/Slider'
import { downloadImage, loadImage, shareImage, useImage } from './utils'
const TOOLBAR_SIZE = 200
const BRUSH_COLOR = 'rgba(189, 255, 1, 0.75)'
interface EditorProps {
file: File
}
interface Line {
size?: number
pts: { x: number; y: number }[]
}
function drawLines(
ctx: CanvasRenderingContext2D,
lines: Line[],
color = BRUSH_COLOR
) {
ctx.strokeStyle = color
ctx.lineCap = 'round'
ctx.lineJoin = 'round'
lines.forEach(line => {
if (!line?.pts.length || !line.size) {
return
}
ctx.lineWidth = line.size
ctx.beginPath()
ctx.moveTo(line.pts[0].x, line.pts[0].y)
line.pts.forEach(pt => ctx.lineTo(pt.x, pt.y))
ctx.stroke()
})
}
export default function Editor(props: EditorProps) {
const { file } = props
const [brushSize, setBrushSize] = useState(40)
const [original, isOriginalLoaded] = useImage(file)
const [renders, setRenders] = useState<HTMLImageElement[]>([])
const [context, setContext] = useState<CanvasRenderingContext2D>()
const [maskCanvas] = useState<HTMLCanvasElement>(() => {
return document.createElement('canvas')
})
const [lines, setLines] = useState<Line[]>([{ pts: [] }])
const [{ x, y }, setCoords] = useState({ x: -1, y: -1 })
const [showBrush, setShowBrush] = useState(false)
const [showOriginal, setShowOriginal] = useState(false)
const [isInpaintingLoading, setIsInpaintingLoading] = useState(false)
const [showSeparator, setShowSeparator] = useState(false)
const [scale, setScale] = useState(1)
const windowSize = useWindowSize()
const draw = useCallback(() => {
if (!context) {
return
}
context.clearRect(0, 0, context.canvas.width, context.canvas.height)
const currRender = renders[renders.length - 1]
if (currRender?.src) {
context.drawImage(currRender, 0, 0)
} else {
context.drawImage(original, 0, 0)
}
const currentLine = lines[lines.length - 1]
drawLines(context, [currentLine])
}, [context, lines, original, renders])
const refreshCanvasMask = useCallback(() => {
if (!context?.canvas.width || !context?.canvas.height) {
throw new Error('canvas has invalid size')
}
maskCanvas.width = context?.canvas.width
maskCanvas.height = context?.canvas.height
const ctx = maskCanvas.getContext('2d')
if (!ctx) {
throw new Error('could not retrieve mask canvas')
}
drawLines(ctx, lines, 'white')
}, [context?.canvas.height, context?.canvas.width, lines, maskCanvas])
// Draw once the original image is loaded
useEffect(() => {
if (!context?.canvas) {
return
}
if (isOriginalLoaded) {
context.canvas.width = original.naturalWidth
context.canvas.height = original.naturalHeight
const rW = windowSize.width / original.naturalWidth
const rH = (windowSize.height - TOOLBAR_SIZE) / original.naturalHeight
if (rW < 1 || rH < 1) {
setScale(Math.min(rW, rH))
} else {
setScale(1)
}
draw()
}
}, [context?.canvas, draw, original, isOriginalLoaded, windowSize])
// Handle mouse interactions
useEffect(() => {
const canvas = context?.canvas
if (!canvas) {
return
}
const onMouseDown = (ev: MouseEvent) => {
if (!original.src) {
return
}
const currLine = lines[lines.length - 1]
currLine.size = brushSize
canvas.addEventListener('mousemove', onMouseDrag)
window.addEventListener('mouseup', onPointerUp)
onPaint(ev.offsetX, ev.offsetY)
}
const onMouseMove = (ev: MouseEvent) => {
setCoords({ x: ev.pageX, y: ev.pageY })
}
const onPaint = (px: number, py: number) => {
const currLine = lines[lines.length - 1]
currLine.pts.push({ x: px, y: py })
draw()
}
const onMouseDrag = (ev: MouseEvent) => {
const px = ev.offsetX
const py = ev.offsetY
onPaint(px, py)
}
const onPointerUp = async () => {
if (!original.src) {
return
}
setIsInpaintingLoading(true)
canvas.removeEventListener('mousemove', onMouseDrag)
window.removeEventListener('mouseup', onPointerUp)
refreshCanvasMask()
try {
const start = Date.now()
const res = await inpaint(file, maskCanvas.toDataURL())
if (!res) {
throw new Error('empty response')
}
// TODO: fix the render if it failed loading
const newRender = new Image()
await loadImage(newRender, res)
renders.push(newRender)
lines.push({ pts: [] } as Line)
setRenders([...renders])
setLines([...lines])
} catch (e: any) {
// eslint-disable-next-line
alert(e.message ? e.message : e.toString())
}
setIsInpaintingLoading(false)
draw()
}
window.addEventListener('mousemove', onMouseMove)
const onTouchMove = (ev: TouchEvent) => {
ev.preventDefault()
ev.stopPropagation()
const currLine = lines[lines.length - 1]
const coords = canvas.getBoundingClientRect()
currLine.pts.push({
x: (ev.touches[0].clientX - coords.x) / scale,
y: (ev.touches[0].clientY - coords.y) / scale,
})
draw()
}
const onPointerStart = (ev: TouchEvent) => {
if (!original.src) {
return
}
const currLine = lines[lines.length - 1]
currLine.size = brushSize
canvas.addEventListener('mousemove', onMouseDrag)
window.addEventListener('mouseup', onPointerUp)
const coords = canvas.getBoundingClientRect()
const px = (ev.touches[0].clientX - coords.x) / scale
const py = (ev.touches[0].clientY - coords.y) / scale
onPaint(px, py)
}
canvas.addEventListener('touchstart', onPointerStart)
canvas.addEventListener('touchmove', onTouchMove)
canvas.addEventListener('touchend', onPointerUp)
canvas.onmouseenter = () => setShowBrush(true)
canvas.onmouseleave = () => setShowBrush(false)
canvas.onmousedown = onMouseDown
return () => {
canvas.removeEventListener('mousemove', onMouseDrag)
window.removeEventListener('mousemove', onMouseMove)
window.removeEventListener('mouseup', onPointerUp)
canvas.removeEventListener('touchstart', onPointerStart)
canvas.removeEventListener('touchmove', onTouchMove)
canvas.removeEventListener('touchend', onPointerUp)
canvas.onmouseenter = null
canvas.onmouseleave = null
canvas.onmousedown = null
}
}, [
brushSize,
context,
file,
draw,
lines,
refreshCanvasMask,
maskCanvas,
original.src,
renders,
original.naturalHeight,
original.naturalWidth,
scale,
])
const undo = useCallback(() => {
const l = lines
l.pop()
l.pop()
setLines([...l, { pts: [] }])
const r = renders
r.pop()
setRenders([...r])
}, [lines, renders])
// Handle Cmd+Z
useEffect(() => {
const handler = (event: KeyboardEvent) => {
if (!renders.length) {
return
}
const isCmdZ = (event.metaKey || event.ctrlKey) && event.key === 'z'
if (isCmdZ) {
event.preventDefault()
undo()
}
}
window.addEventListener('keydown', handler)
return () => {
window.removeEventListener('keydown', handler)
}
}, [renders, undo])
function download() {
const base64 = context?.canvas.toDataURL(file.type)
if (!base64) {
throw new Error('could not get canvas data')
}
const name = file.name.replace(/(\.[\w\d_-]+)$/i, '_cleanup$1')
downloadImage(base64, name)
}
return (
<div
className={[
'flex flex-col items-center',
isInpaintingLoading
? 'animate-pulse-fast pointer-events-none transition-opacity'
: '',
scale !== 1 ? 'pb-24' : '',
].join(' ')}
style={{
height: scale !== 1 ? original.naturalHeight * scale : undefined,
}}
>
<div
className={[scale !== 1 ? '' : 'relative'].join(' ')}
style={{ transform: `scale(${scale})`, transformOrigin: 'top center' }}
>
<canvas
className="rounded-sm"
style={showBrush ? { cursor: 'none' } : {}}
ref={r => {
if (r && !context) {
const ctx = r.getContext('2d')
if (ctx) {
setContext(ctx)
}
}
}}
/>
<div
className={[
'absolute top-0 right-0 pointer-events-none',
'overflow-hidden',
'border-primary',
showSeparator ? 'border-l-4' : '',
// showOriginal ? 'border-opacity-100' : 'border-opacity-0',
].join(' ')}
style={{
width: showOriginal
? `${Math.round(original.naturalWidth)}px`
: '0px',
height: original.naturalHeight,
transitionProperty: 'width, height',
transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)',
transitionDuration: '300ms',
}}
>
<img
className="absolute right-0"
src={original.src}
alt="original"
width={`${original.naturalWidth}px`}
height={`${original.naturalHeight}px`}
style={{
width: `${original.naturalWidth}px`,
height: `${original.naturalHeight}px`,
maxWidth: 'none',
}}
/>
</div>
</div>
{showBrush && (
<div
className="hidden sm:block absolute rounded-full border border-primary bg-primary bg-opacity-80 pointer-events-none"
style={{
width: `${brushSize * scale}px`,
height: `${brushSize * scale}px`,
left: `${x}px`,
top: `${y}px`,
transform: 'translate(-50%, -50%)',
}}
/>
)}
<div
className={[
'flex items-center w-full max-w-3xl',
'space-x-3 sm:space-x-5',
'p-6',
scale !== 1
? 'absolute bottom-0 justify-evenly'
: 'relative justify-evenly sm:justify-between',
].join(' ')}
>
<Slider
label={
<span>
<span className="hidden md:inline">Brush</span> Size
</span>
}
min={10}
max={150}
value={brushSize}
onChange={setBrushSize}
/>
{renders.length ? (
<>
<Button
icon={
<svg
width="19"
height="9"
viewBox="0 0 19 9"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="w-6 h-6"
>
<path
d="M2 1C2 0.447715 1.55228 0 1 0C0.447715 0 0 0.447715 0 1H2ZM1 8H0V9H1V8ZM8 9C8.55228 9 9 8.55229 9 8C9 7.44771 8.55228 7 8 7V9ZM16.5963 7.42809C16.8327 7.92721 17.429 8.14016 17.9281 7.90374C18.4272 7.66731 18.6402 7.07103 18.4037 6.57191L16.5963 7.42809ZM16.9468 5.83205L17.8505 5.40396L16.9468 5.83205ZM0 1V8H2V1H0ZM1 9H8V7H1V9ZM1.66896 8.74329L6.66896 4.24329L5.33104 2.75671L0.331035 7.25671L1.66896 8.74329ZM16.043 6.26014L16.5963 7.42809L18.4037 6.57191L17.8505 5.40396L16.043 6.26014ZM6.65079 4.25926C9.67554 1.66661 14.3376 2.65979 16.043 6.26014L17.8505 5.40396C15.5805 0.61182 9.37523 -0.710131 5.34921 2.74074L6.65079 4.25926Z"
fill="currentColor"
/>
</svg>
}
onClick={undo}
/>
<Button
icon={<EyeIcon className="w-6 h-6" />}
onDown={ev => {
ev.preventDefault()
setShowSeparator(true)
setShowOriginal(true)
}}
onUp={() => {
setShowOriginal(false)
setTimeout(() => setShowSeparator(false), 300)
}}
>
{windowSize.width > 640 ? 'Original' : undefined}
</Button>
</>
) : (
<></>
)}
<Button
primary
icon={<DownloadIcon className="w-6 h-6" />}
disabled={!renders.length}
onClick={download}
>
{windowSize.width > 640 ? 'Download' : undefined}
</Button>
</div>
</div>
)
}

View File

@ -0,0 +1,19 @@
import { dataURItoBlob } from '../utils'
export const API_ENDPOINT = `${process.env.REACT_APP_INPAINTING_URL}/inpaint`
export default async function inpaint(imageFile: File, maskBase64: string) {
const fd = new FormData()
fd.append('image', imageFile)
const mask = dataURItoBlob(maskBase64)
fd.append('mask', mask)
const res = await fetch(API_ENDPOINT, {
method: 'POST',
body: fd,
}).then(async r => {
return r.blob()
})
return URL.createObjectURL(res)
}

View File

@ -0,0 +1,62 @@
import React, { ReactNode, useState } from 'react'
interface ButtonProps {
children?: ReactNode
className?: string
icon?: ReactNode
primary?: boolean
disabled?: boolean
onClick?: () => void
onDown?: (ev: PointerEvent) => void
onUp?: (ev: PointerEvent) => void
}
export default function Button(props: ButtonProps) {
const {
children,
className,
disabled,
icon,
primary,
onClick,
onDown,
onUp,
} = props
const [active, setActive] = useState(false)
let background = ''
if (primary && !disabled) {
background = 'bg-primary hover:bg-black hover:text-white'
}
if (active) {
background = 'bg-black text-white'
}
if (!primary && !active) {
background = 'hover:bg-primary'
}
return (
<div
role="button"
onKeyDown={onClick}
onClick={onClick}
onPointerDown={(ev: React.PointerEvent<HTMLDivElement>) => {
setActive(true)
onDown?.(ev.nativeEvent)
}}
onPointerUp={(ev: React.PointerEvent<HTMLDivElement>) => {
setActive(false)
onUp?.(ev.nativeEvent)
}}
tabIndex={-1}
className={[
'inline-flex py-3 px-5 rounded-md cursor-pointer',
children ? 'space-x-3' : '',
background,
disabled ? 'pointer-events-none opacity-50' : '',
className,
].join(' ')}
>
{icon}
<span className="whitespace-nowrap select-none">{children}</span>
</div>
)
}

View File

@ -0,0 +1,135 @@
import React, { useState } from 'react'
type FileSelectProps = {
onSelection: (file: File) => void
}
export default function FileSelect(props: FileSelectProps) {
const { onSelection } = props
const [dragHover, setDragHover] = useState(false)
const [uploadElemId] = useState(`file-upload-${Math.random().toString()}`)
function onFileSelected(file: File) {
if (!file) {
return
}
// Skip non-image files
const isImage = file.type.match('image.*')
if (!isImage) {
return
}
try {
// Check if file is larger than 20mb
if (file.size > 20 * 1024 * 1024) {
throw new Error('file too large')
}
onSelection(file)
} catch (e) {
// eslint-disable-next-line
alert(`error: ${(e as any).message}`)
}
}
async function getFile(entry: any): Promise<File> {
return new Promise(resolve => {
entry.file((file: File) => resolve(file))
})
}
/* eslint-disable no-await-in-loop */
// Drop handler function to get all files
async function getAllFileEntries(items: DataTransferItemList) {
const fileEntries: Array<File> = []
// Use BFS to traverse entire directory/file structure
const queue = []
// Unfortunately items is not iterable i.e. no forEach
for (let i = 0; i < items.length; i += 1) {
queue.push(items[i].webkitGetAsEntry())
}
while (queue.length > 0) {
const entry = queue.shift()
if (entry?.isFile) {
// Only append images
const file = await getFile(entry)
fileEntries.push(file)
} else if (entry?.isDirectory) {
queue.push(
...(await readAllDirectoryEntries((entry as any).createReader()))
)
}
}
return fileEntries
}
// Get all the entries (files or sub-directories) in a directory
// by calling readEntries until it returns empty array
async function readAllDirectoryEntries(directoryReader: any) {
const entries = []
let readEntries = await readEntriesPromise(directoryReader)
while (readEntries.length > 0) {
entries.push(...readEntries)
readEntries = await readEntriesPromise(directoryReader)
}
return entries
}
/* eslint-enable no-await-in-loop */
// Wrap readEntries in a promise to make working with readEntries easier
// readEntries will return only some of the entries in a directory
// e.g. Chrome returns at most 100 entries at a time
async function readEntriesPromise(directoryReader: any): Promise<any> {
return new Promise((resolve, reject) => {
directoryReader.readEntries(resolve, reject)
})
}
async function handleDrop(ev: React.DragEvent) {
ev.preventDefault()
const items = await getAllFileEntries(ev.dataTransfer.items)
setDragHover(false)
onFileSelected(items[0])
}
return (
<label
htmlFor={uploadElemId}
className="block w-full h-full group relative cursor-pointer rounded-md font-medium focus-within:outline-none"
>
<div
className={[
'w-full h-full flex items-center justify-center px-6 pt-5 pb-6 text-md',
'border-2 border-dashed rounded-md',
'hover:border-black hover:bg-primary',
'text-center',
dragHover ? 'border-black bg-primary' : 'bg-gray-100 border-gray-300',
].join(' ')}
onDrop={handleDrop}
onDragOver={ev => {
ev.stopPropagation()
ev.preventDefault()
setDragHover(true)
}}
onDragLeave={() => setDragHover(false)}
>
<input
id={uploadElemId}
name={uploadElemId}
type="file"
className="sr-only"
onChange={ev => {
const file = ev.currentTarget.files?.[0]
if (file) {
onFileSelected(file)
}
}}
accept="image/png, image/jpeg"
/>
<p className="hidden sm:block">Click here or drag an image file</p>
<p className="sm:hidden">Tap here to load your picture</p>
</div>
</label>
)
}

View File

@ -0,0 +1,15 @@
import React from 'react'
interface LinkProps {
children: string
href: string
}
export default function Link(props: LinkProps) {
const { children, href } = props
return (
<a href={href} className="font-black underline">
{children}
</a>
)
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,74 @@
import React from 'react'
export default function MadeWidthBadge() {
return (
<svg
width="101"
height="43"
viewBox="0 0 101 43"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M0.158081 28.5402C0.158081 26.12 2.07738 24.1581 4.44495 24.1581H5.62173C6.08596 24.1581 6.46229 24.5428 6.46229 25.0173V25.7907C6.46229 26.2652 6.08596 26.6499 5.62173 26.6499H4.44495C3.47006 26.6499 2.4276 27.5437 2.4276 28.5402V29.7432C2.4276 30.2177 2.05127 30.6024 1.58704 30.6024H0.998643C0.534413 30.6024 0.158081 30.2177 0.158081 29.7432L0.158081 28.5402Z"
fill="black"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M15.6244 35.2423C15.6244 37.6625 13.7051 39.6244 11.3376 39.6244H10.1608C9.69654 39.6244 9.3202 39.2397 9.3202 38.7652V37.9919C9.3202 37.5173 9.69654 37.1326 10.1608 37.1326H11.3376C12.3124 37.1326 13.3549 36.2388 13.3549 35.2423V34.0394C13.3549 33.5648 13.7312 33.1801 14.1955 33.1801H14.7839C15.2481 33.1801 15.6244 33.5648 15.6244 34.0394V35.2423Z"
fill="black"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M11.3376 24.1581C13.7051 24.1581 15.6244 26.12 15.6244 28.5402V29.7432C15.6244 30.2177 15.2481 30.6024 14.7839 30.6024H14.0274C13.5631 30.6024 13.1868 30.2177 13.1868 29.7432V28.5402C13.1868 27.5437 12.3124 26.478 11.3376 26.478H10.1608C9.69654 26.478 9.3202 26.0934 9.3202 25.6188V25.0173C9.3202 24.5428 9.69654 24.1581 10.1608 24.1581L11.3376 24.1581Z"
fill="black"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M4.44495 39.6244C2.07738 39.6244 0.158081 37.6625 0.158081 35.2423L0.158082 34.0394C0.158082 33.5648 0.534414 33.1801 0.998643 33.1801H1.75515C2.21938 33.1801 2.59571 33.5648 2.59571 34.0394L2.59571 35.2423C2.59571 36.2388 3.47006 37.3045 4.44495 37.3045L5.62173 37.3045C6.08596 37.3045 6.46229 37.6892 6.46229 38.1637V38.7652C6.46229 39.2397 6.08596 39.6244 5.62173 39.6244L4.44495 39.6244Z"
fill="black"
/>
<path
d="M36.6965 29.5082C36.289 26.5653 34.0253 24.8578 31.0888 24.8578C27.6285 24.8578 25.0219 27.3802 25.0219 31.662C25.0219 35.9373 27.5961 38.4663 31.0888 38.4663C34.2387 38.4663 36.3279 36.4289 36.6965 33.8999L34.2775 33.887C33.9735 35.4393 32.7058 36.3125 31.1212 36.3125C28.9738 36.3125 27.4345 34.7019 27.4345 31.662C27.4345 28.6738 28.9609 27.0116 31.1276 27.0116C32.7382 27.0116 33.9994 27.9236 34.2775 29.5082H36.6965Z"
fill="black"
/>
<path
d="M41.1319 25.0389H38.7905V38.2852H41.1319V25.0389Z"
fill="black"
/>
<path
d="M43.5396 38.2852H45.881V28.3504H43.5396V38.2852ZM44.7168 26.9404C45.4606 26.9404 46.0686 26.3712 46.0686 25.6727C46.0686 24.9677 45.4606 24.3985 44.7168 24.3985C43.9665 24.3985 43.3585 24.9677 43.3585 25.6727C43.3585 26.3712 43.9665 26.9404 44.7168 26.9404Z"
fill="black"
/>
<path
d="M48.2887 42.0107H50.6301V36.7199H50.7271C51.0958 37.4443 51.8654 38.4598 53.573 38.4598C55.9144 38.4598 57.6672 36.6035 57.6672 33.3307C57.6672 30.0192 55.8626 28.2211 53.5665 28.2211C51.8137 28.2211 51.0828 29.2754 50.7271 29.9933H50.5913V28.3504H48.2887V42.0107ZM50.5848 33.3178C50.5848 31.3904 51.4127 30.1421 52.9197 30.1421C54.4785 30.1421 55.2805 31.468 55.2805 33.3178C55.2805 35.1806 54.4656 36.5388 52.9197 36.5388C51.4256 36.5388 50.5848 35.2453 50.5848 33.3178Z"
fill="black"
/>
<path
d="M64.2645 38.2852C68.3005 38.2852 70.6936 35.7886 70.6936 31.6491C70.6936 27.5225 68.3005 25.0389 64.355 25.0389H59.7757V38.2852H64.2645ZM62.1753 36.209V27.1151H64.2192C66.9099 27.1151 68.3134 28.6156 68.3134 31.6491C68.3134 34.6955 66.9099 36.209 64.1481 36.209H62.1753Z"
fill="black"
/>
<path
d="M72.8701 38.2852H75.2115V32.4446C75.2115 31.1834 76.1622 30.2908 77.4494 30.2908C77.8439 30.2908 78.3355 30.362 78.536 30.4266V28.2728C78.3225 28.234 77.9539 28.2081 77.6951 28.2081C76.5568 28.2081 75.606 28.8549 75.2438 30.0062H75.1403V28.3504H72.8701V38.2852Z"
fill="black"
/>
<path
d="M84.0159 38.4792C86.9265 38.4792 88.7763 36.4289 88.7763 33.3566C88.7763 30.2779 86.9265 28.2211 84.0159 28.2211C81.1054 28.2211 79.2555 30.2779 79.2555 33.3566C79.2555 36.4289 81.1054 38.4792 84.0159 38.4792ZM84.0289 36.6035C82.4183 36.6035 81.6293 35.1676 81.6293 33.3501C81.6293 31.5327 82.4183 30.0774 84.0289 30.0774C85.6135 30.0774 86.4026 31.5327 86.4026 33.3501C86.4026 35.1676 85.6135 36.6035 84.0289 36.6035Z"
fill="black"
/>
<path
d="M90.7636 42.0107H93.105V36.7199H93.202C93.5707 37.4443 94.3404 38.4598 96.0479 38.4598C98.3893 38.4598 100.142 36.6035 100.142 33.3307C100.142 30.0192 98.3375 28.2211 96.0414 28.2211C94.2886 28.2211 93.5577 29.2754 93.202 29.9933H93.0662V28.3504H90.7636V42.0107ZM93.0597 33.3178C93.0597 31.3904 93.8876 30.1421 95.3946 30.1421C96.9534 30.1421 97.7554 31.468 97.7554 33.3178C97.7554 35.1806 96.9405 36.5388 95.3946 36.5388C93.9005 36.5388 93.0597 35.2453 93.0597 33.3178Z"
fill="black"
/>
<path
d="M1.03871 3.54545V13H2.39595V6.15376H2.48366L5.27202 12.9862H6.39844L9.18679 6.15838H9.2745V13H10.6317V3.54545H8.90057L5.89062 10.8949H5.77983L2.76989 3.54545H1.03871ZM14.6585 13.157C15.8311 13.157 16.4912 12.5614 16.7544 12.0305H16.8097V13H18.1578V8.29119C18.1578 6.22763 16.5328 5.81676 15.4063 5.81676C14.123 5.81676 12.9411 6.33381 12.4795 7.62642L13.7767 7.92188C13.9798 7.41868 14.4969 6.93395 15.4248 6.93395C16.3158 6.93395 16.7728 7.40021 16.7728 8.20348V8.2358C16.7728 8.73899 16.2558 8.72976 14.9816 8.87749C13.6382 9.03445 12.2625 9.3853 12.2625 10.9964C12.2625 12.3906 13.3105 13.157 14.6585 13.157ZM14.9585 12.049C14.1784 12.049 13.6151 11.6982 13.6151 11.0149C13.6151 10.2763 14.2707 10.0131 15.0693 9.90696C15.5171 9.84695 16.5789 9.72692 16.7774 9.52841V10.4425C16.7774 11.2827 16.108 12.049 14.9585 12.049ZM22.6507 13.1385C23.9434 13.1385 24.4512 12.3491 24.7005 11.8967H24.8159V13H26.1639V3.54545H24.7836V7.05859H24.7005C24.4512 6.62003 23.9803 5.81676 22.66 5.81676C20.9473 5.81676 19.687 7.16939 19.687 9.46839C19.687 11.7628 20.9288 13.1385 22.6507 13.1385ZM22.9554 11.9613C21.7228 11.9613 21.0811 10.8764 21.0811 9.45455C21.0811 8.04652 21.709 6.98935 22.9554 6.98935C24.1603 6.98935 24.8066 7.97266 24.8066 9.45455C24.8066 10.9457 24.1465 11.9613 22.9554 11.9613ZM31.1901 13.1431C32.7366 13.1431 33.8307 12.3814 34.1446 11.2273L32.8382 10.9918C32.5889 11.6612 31.9887 12.0028 31.2039 12.0028C30.0221 12.0028 29.2281 11.2365 29.1911 9.87003H34.2323V9.38068C34.2323 6.81854 32.6997 5.81676 31.0931 5.81676C29.1173 5.81676 27.8154 7.32173 27.8154 9.50071C27.8154 11.7028 29.0988 13.1431 31.1901 13.1431ZM29.1958 8.83594C29.2512 7.82955 29.9806 6.95703 31.1024 6.95703C32.1734 6.95703 32.8751 7.75107 32.8797 8.83594H29.1958ZM40.7416 13H42.145L43.5853 7.88033H43.6915L45.1318 13H46.5399L48.6219 5.90909H47.1954L45.8151 11.0934H45.7458L44.3609 5.90909H42.9344L41.5402 11.1165H41.471L40.0814 5.90909H38.6549L40.7416 13ZM49.9318 13H51.3121V5.90909H49.9318V13ZM50.6289 4.81499C51.1044 4.81499 51.5014 4.44567 51.5014 3.99325C51.5014 3.54084 51.1044 3.1669 50.6289 3.1669C50.1488 3.1669 49.7564 3.54084 49.7564 3.99325C49.7564 4.44567 50.1488 4.81499 50.6289 4.81499ZM56.4791 5.90909H55.0249V4.21023H53.6446V5.90909H52.6059V7.01705H53.6446V11.2042C53.64 12.4922 54.6233 13.1154 55.7128 13.0923C56.1514 13.0877 56.4468 13.0046 56.6084 12.9446L56.3591 11.8043C56.2668 11.8228 56.096 11.8643 55.8744 11.8643C55.4266 11.8643 55.0249 11.7166 55.0249 10.918V7.01705H56.4791V5.90909ZM59.5387 8.78977C59.5387 7.65874 60.2543 7.01243 61.2376 7.01243C62.1886 7.01243 62.7564 7.61719 62.7564 8.65589V13H64.1367V8.4897C64.1367 6.72159 63.1673 5.81676 61.7085 5.81676C60.6051 5.81676 59.9403 6.29688 59.608 7.06321H59.5202V3.54545H58.1584V13H59.5387V8.78977ZM75.1389 13.1477L79.8847 8.40199C81.0203 7.26633 81.0111 5.39204 79.8847 4.28409C78.7306 3.14844 76.9255 3.15767 75.7668 4.28409L75.1389 4.89347L74.5111 4.28409C73.3524 3.15767 71.5473 3.14844 70.3932 4.28409C69.2668 5.39204 69.2575 7.26633 70.3932 8.40199L75.1389 13.1477ZM86.2623 13H87.6104V11.8967H87.7258C87.9751 12.3491 88.4829 13.1385 89.7755 13.1385C91.4928 13.1385 92.7393 11.7628 92.7393 9.46839C92.7393 7.16939 91.4743 5.81676 89.7616 5.81676C88.4459 5.81676 87.9704 6.62003 87.7258 7.05859H87.6427V3.54545H86.2623V13ZM87.615 9.45455C87.615 7.97266 88.2613 6.98935 89.4662 6.98935C90.7172 6.98935 91.3451 8.04652 91.3451 9.45455C91.3451 10.8764 90.6988 11.9613 89.4662 11.9613C88.2797 11.9613 87.615 10.9457 87.615 9.45455ZM94.9078 15.6591C96.0481 15.6591 96.7683 15.0636 97.1792 13.9464L100.111 5.92294L98.6195 5.90909L96.8237 11.4119H96.7498L94.954 5.90909H93.4767L96.0712 13.0923L95.9004 13.5632C95.5495 14.505 95.0556 14.5835 94.2985 14.3757L93.9661 15.5067C94.1323 15.5806 94.4924 15.6591 94.9078 15.6591Z"
fill="black"
/>
</svg>
)
}

View File

@ -0,0 +1,19 @@
import React, { ReactNode } from 'react'
interface ModalProps {
children?: ReactNode
}
export default function Modal(props: ModalProps) {
const { children } = props
return (
<div
className={[
'absolute w-full h-full flex justify-center items-center',
'bg-white bg-opacity-40 backdrop-filter backdrop-blur-md',
].join(' ')}
>
<div className="bg-primary p-16 max-w-4xl">{children}</div>
</div>
)
}

View File

@ -0,0 +1,38 @@
import React from 'react'
type SliderProps = {
label?: any
value?: number
min?: number
max?: number
onChange: (value: number) => void
}
export default function Slider(props: SliderProps) {
const { value, onChange, label, min, max } = props
const step = ((max || 100) - (min || 0)) / 100
return (
<div className="inline-flex items-center space-x-4 text-black">
<span>{label}</span>
<input
className={[
'appearance-none rounded-lg h-4',
'bg-primary',
'w-24 md:w-auto',
].join(' ')}
type="range"
step={step}
min={min}
max={max}
value={value}
onChange={ev => {
ev.preventDefault()
ev.stopPropagation()
onChange(parseInt(ev.currentTarget.value, 10))
}}
/>
</div>
)
}

View File

@ -0,0 +1,6 @@
import React from 'react'
import ReactDOM from 'react-dom'
import './styles/index.css'
import App from './App'
ReactDOM.render(<App />, document.getElementById('root'))

View File

@ -0,0 +1 @@
/// <reference types="react-scripts" />

View File

@ -0,0 +1,5 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom/extend-expect'

View File

@ -0,0 +1,46 @@
@tailwind base;
/* Your own custom base styles */
/* Start purging... */
@tailwind components;
/* Stop purging. */
/* Your own custom component styles */
/* Start purging... */
@tailwind utilities;
/* Stop purging. */
/* Your own custom utilities */
@import url('https://fonts.googleapis.com/css2?family=Work+Sans:wght@600;900&display=swap');
html,
body {
width: 100%;
height: 100%;
font-family: 'Work Sans';
font-weight: 600;
overflow: hidden;
}
input[type='range']::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 17px;
height: 17px;
background: black;
border-radius: 50%;
}
*:not(input):not(textarea) {
user-select: none; /* disable selection/Copy of UIWebView */
-webkit-user-select: none; /* disable selection/Copy of UIWebView */
-webkit-touch-callout: none; /* disable the IOS popup when long-press on a link */
}
@supports (-webkit-touch-callout: none) {
.full-visible-h-safari {
height: calc(100% - 80px); /* -webkit-fill-available;*/
}
}

View File

@ -0,0 +1,158 @@
import { useEffect, useState } from 'react'
export function dataURItoBlob(dataURI: string) {
const mime = dataURI.split(',')[0].split(':')[1].split(';')[0]
const binary = atob(dataURI.split(',')[1])
const array = []
for (let i = 0; i < binary.length; i += 1) {
array.push(binary.charCodeAt(i))
}
return new Blob([new Uint8Array(array)], { type: mime })
}
// const dataURItoBlob = (dataURI: string) => {
// const bytes =
// dataURI.split(',')[0].indexOf('base64') >= 0
// ? atob(dataURI.split(',')[1])
// : unescape(dataURI.split(',')[1])
// const mime = dataURI.split(',')[0].split(':')[1].split(';')[0]
// const max = bytes.length
// const ia = new Uint8Array(max)
// for (var i = 0; i < max; i++) ia[i] = bytes.charCodeAt(i)
// return new Blob([ia], { type: mime })
// }
export function downloadImage(uri: string, name: string) {
const link = document.createElement('a')
link.href = uri
link.download = name
// this is necessary as link.click() does not work on the latest firefox
link.dispatchEvent(
new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window,
})
)
setTimeout(() => {
// For Firefox it is necessary to delay revoking the ObjectURL
// window.URL.revokeObjectURL(base64)
link.remove()
}, 100)
}
export function shareImage(base64: string, name: string) {
const blob = dataURItoBlob(base64)
const filesArray = [new File([blob], name, { type: 'image/jpeg' })]
const shareData = {
files: filesArray,
}
// eslint-disable-nextline
const nav: any = navigator
const canShare = nav.canShare && nav.canShare(shareData)
const userAgent = navigator.userAgent || navigator.vendor
const isMobile = /android|iPad|iPhone|iPod/i.test(userAgent)
if (canShare && isMobile) {
navigator.share(shareData)
return true
}
return false
}
export function loadImage(image: HTMLImageElement, src: string) {
return new Promise((resolve, reject) => {
const initSRC = image.src
const img = image
img.onload = resolve
img.onerror = err => {
img.src = initSRC
reject(err)
}
img.src = src
})
}
export function useImage(file: File): [HTMLImageElement, boolean] {
const [image] = useState(new Image())
const [isLoaded, setIsLoaded] = useState(false)
useEffect(() => {
image.onload = () => {
setIsLoaded(true)
}
setIsLoaded(false)
image.src = URL.createObjectURL(file)
return () => {
image.onload = null
}
}, [file, image])
return [image, isLoaded]
}
// https://stackoverflow.com/questions/23945494/use-html5-to-resize-an-image-before-upload
interface ResizeImageFileResult {
file: File
resized: boolean
originalWidth?: number
originalHeight?: number
}
export function resizeImageFile(
file: File,
maxSize: number
): Promise<ResizeImageFileResult> {
const reader = new FileReader()
const image = new Image()
const canvas = document.createElement('canvas')
const resize = (): ResizeImageFileResult => {
let { width, height } = image
if (width > height) {
if (width > maxSize) {
height *= maxSize / width
width = maxSize
}
} else if (height > maxSize) {
width *= maxSize / height
height = maxSize
}
if (width === image.width && height === image.height) {
return { file, resized: false }
}
canvas.width = width
canvas.height = height
const ctx = canvas.getContext('2d')
if (!ctx) {
throw new Error('could not get context')
}
canvas.getContext('2d')?.drawImage(image, 0, 0, width, height)
const dataUrl = canvas.toDataURL('image/jpeg')
const blob = dataURItoBlob(dataUrl)
const f = new File([blob], file.name, {
type: file.type,
})
return {
file: f,
resized: true,
originalWidth: image.width,
originalHeight: image.height,
}
}
return new Promise((resolve, reject) => {
if (!file.type.match(/image.*/)) {
reject(new Error('Not an image'))
return
}
reader.onload = (readerEvent: any) => {
image.onload = () => resolve(resize())
image.src = readerEvent.target.result
}
reader.readAsDataURL(file)
})
}

View File

@ -0,0 +1,37 @@
module.exports = {
mode: 'jit',
theme: {
extend: {
animation: {
'pulse-fast': 'pulse 0.7s cubic-bezier(0.4, 0, 0.6, 1) infinite',
},
colors: {
primary: '#BDFF01',
},
keyframes: {
pulse: {
'0%, 100%': { opacity: 0.8 },
'50%': { opacity: 0.7 },
},
},
},
},
variants: {},
plugins: [],
purge: {
// Filenames to scan for classes
content: [
'./src/**/*.html',
'./src/**/*.js',
'./src/**/*.jsx',
'./src/**/*.ts',
'./src/**/*.tsx',
'./public/index.html',
],
// Options passed to PurgeCSS
options: {
// Whitelist specific selectors by name
// safelist: [],
},
},
}

View File

@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
}

12354
lama_cleaner/app/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

65
lama_cleaner/helper.py Normal file
View File

@ -0,0 +1,65 @@
import os
import sys
from urllib.parse import urlparse
import cv2
import numpy as np
import torch
from torch.hub import download_url_to_file, get_dir
LAMA_MODEL_URL = os.environ.get(
"LAMA_MODEL_URL",
"https://github.com/Sanster/models/releases/download/add_big_lama/big-lama.pt",
)
def download_model(url=LAMA_MODEL_URL):
parts = urlparse(url)
hub_dir = get_dir()
model_dir = os.path.join(hub_dir, "checkpoints")
filename = os.path.basename(parts.path)
cached_file = os.path.join(model_dir, filename)
if not os.path.exists(cached_file):
sys.stderr.write('Downloading: "{}" to {}\n'.format(url, cached_file))
hash_prefix = None
download_url_to_file(url, cached_file, hash_prefix, progress=True)
return cached_file
def ceil_modulo(x, mod):
if x % mod == 0:
return x
return (x // mod + 1) * mod
def numpy_to_bytes(image_numpy: np.ndarray) -> bytes:
data = cv2.imencode(".jpg", image_numpy)[1]
image_bytes = data.tobytes()
return image_bytes
def load_img(img_bytes, gray: bool = False, norm: bool = True):
nparr = np.frombuffer(img_bytes, np.uint8)
if gray:
np_img = cv2.imdecode(nparr, cv2.IMREAD_GRAYSCALE)[:, :, np.newaxis]
else:
np_img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
np_img = cv2.cvtColor(np_img, cv2.COLOR_BGR2RGB)
if norm:
np_img = np.transpose(np_img, (2, 0, 1))
np_img = np_img.astype("float32") / 255
return np_img
def pad_img_to_modulo(img, mod):
channels, height, width = img.shape
out_height = ceil_modulo(height, mod)
out_width = ceil_modulo(width, mod)
return np.pad(
img,
((0, 0), (0, out_height - height), (0, out_width - width)),
mode="symmetric",
)

100
main.py Normal file
View File

@ -0,0 +1,100 @@
#!/usr/bin/env python3
import io
import os
import time
import argparse
import cv2
import numpy as np
import torch
from flask import Flask, request, send_file
from flask_cors import CORS
from lama_cleaner.helper import (
download_model,
load_img,
numpy_to_bytes,
pad_img_to_modulo,
)
NUM_THREADS = "4"
os.environ["OMP_NUM_THREADS"] = NUM_THREADS
os.environ["OPENBLAS_NUM_THREADS"] = NUM_THREADS
os.environ["MKL_NUM_THREADS"] = NUM_THREADS
os.environ["VECLIB_MAXIMUM_THREADS"] = NUM_THREADS
os.environ["NUMEXPR_NUM_THREADS"] = NUM_THREADS
BUILD_DIR = os.environ.get("LAMA_CLEANER_BUILD_DIR", "./lama_cleaner/app/build")
app = Flask(__name__, static_folder=os.path.join(BUILD_DIR, "static"))
app.config["JSON_AS_ASCII"] = False
CORS(app)
model = None
device = None
@app.route("/inpaint", methods=["POST"])
def process():
input = request.files
image = load_img(input["image"].read())
mask = load_img(input["mask"].read(), gray=True)
res_np_img = run(image, mask)
return send_file(
io.BytesIO(numpy_to_bytes(res_np_img)),
mimetype="image/png",
as_attachment=True,
attachment_filename="result.png",
)
@app.route("/")
def index():
return send_file(os.path.join(BUILD_DIR, "index.html"))
def run(image, mask):
"""
image: [C, H, W]
"""
origin_height, origin_width = image.shape[1:]
image = pad_img_to_modulo(image, mod=8)
mask = pad_img_to_modulo(mask, mod=8)
mask = (mask > 0) * 1
image = torch.from_numpy(image).unsqueeze(0).to(device)
mask = torch.from_numpy(mask).unsqueeze(0).to(device)
start = time.time()
inpainted_image = model(image, mask)
print(
f"inpainted image shape: {inpainted_image.shape} process time: {(time.time() - start)*1000}ms"
)
cur_res = inpainted_image[0].permute(1, 2, 0).detach().cpu().numpy()
cur_res = cur_res[0:origin_height, 0:origin_width, :]
cur_res = np.clip(cur_res * 255, 0, 255).astype("uint8")
cur_res = cv2.cvtColor(cur_res, cv2.COLOR_RGB2BGR)
return cur_res
def get_args_parser():
parser = argparse.ArgumentParser()
parser.add_argument("--port", default=8080, type=int)
parser.add_argument("--device", default="cuda", type=str)
return parser.parse_args()
def main():
global model
global device
args = get_args_parser()
device = torch.device(args.device)
model_path = download_model()
model = torch.jit.load(model_path, map_location="cpu")
model = model.to(device)
app.run(host="0.0.0.0", port=args.port, debug=False)
if __name__ == "__main__":
main()

4
requirements.txt Normal file
View File

@ -0,0 +1,4 @@
torch>=1.8.2
opencv-python
flask_cors
flask

1
setup.py Normal file
View File

@ -0,0 +1 @@
# TODO: make this a python package