init
This commit is contained in:
commit
4e027f81e6
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
.DS_Store
|
||||||
|
**/__pycache__
|
201
LICENSE
Normal file
201
LICENSE
Normal 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
19
README.md
Normal 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
1
lama_cleaner/app/.env
Normal file
@ -0,0 +1 @@
|
|||||||
|
REACT_APP_INPAINTING_URL=""
|
50
lama_cleaner/app/.eslintrc.json
Normal file
50
lama_cleaner/app/.eslintrc.json
Normal 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
27
lama_cleaner/app/.gitignore
vendored
Normal 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
|
6
lama_cleaner/app/.prettierrc
Normal file
6
lama_cleaner/app/.prettierrc
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"singleQuote": true,
|
||||||
|
"semi": false,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"arrowParens": "avoid"
|
||||||
|
}
|
201
lama_cleaner/app/LICENSE
Normal file
201
lama_cleaner/app/LICENSE
Normal 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.
|
20
lama_cleaner/app/build/asset-manifest.json
Normal file
20
lama_cleaner/app/build/asset-manifest.json
Normal 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"
|
||||||
|
]
|
||||||
|
}
|
1
lama_cleaner/app/build/index.html
Normal file
1
lama_cleaner/app/build/index.html
Normal 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
3
lama_cleaner/app/build/static/js/2.1884f63d.chunk.js
Normal file
3
lama_cleaner/app/build/static/js/2.1884f63d.chunk.js
Normal file
File diff suppressed because one or more lines are too long
@ -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.
|
||||||
|
*/
|
1
lama_cleaner/app/build/static/js/2.1884f63d.chunk.js.map
Normal file
1
lama_cleaner/app/build/static/js/2.1884f63d.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
2
lama_cleaner/app/build/static/js/main.56b43b57.chunk.js
Normal file
2
lama_cleaner/app/build/static/js/main.56b43b57.chunk.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -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
65
lama_cleaner/app/package.json
Normal file
65
lama_cleaner/app/package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
6
lama_cleaner/app/postcss.config.js
Normal file
6
lama_cleaner/app/postcss.config.js
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: [
|
||||||
|
require('tailwindcss'),
|
||||||
|
require('postcss-preset-env'),
|
||||||
|
],
|
||||||
|
};
|
33
lama_cleaner/app/public/index.html
Normal file
33
lama_cleaner/app/public/index.html
Normal 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>
|
81
lama_cleaner/app/src/App.tsx
Normal file
81
lama_cleaner/app/src/App.tsx
Normal 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
|
409
lama_cleaner/app/src/Editor.tsx
Normal file
409
lama_cleaner/app/src/Editor.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
19
lama_cleaner/app/src/adapters/inpainting.ts
Normal file
19
lama_cleaner/app/src/adapters/inpainting.ts
Normal 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)
|
||||||
|
}
|
62
lama_cleaner/app/src/components/Button.tsx
Normal file
62
lama_cleaner/app/src/components/Button.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
135
lama_cleaner/app/src/components/FileSelect.tsx
Normal file
135
lama_cleaner/app/src/components/FileSelect.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
15
lama_cleaner/app/src/components/Link.tsx
Normal file
15
lama_cleaner/app/src/components/Link.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
116
lama_cleaner/app/src/components/Logo.tsx
Normal file
116
lama_cleaner/app/src/components/Logo.tsx
Normal file
File diff suppressed because one or more lines are too long
74
lama_cleaner/app/src/components/MadeWidthBadge.tsx
Normal file
74
lama_cleaner/app/src/components/MadeWidthBadge.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
19
lama_cleaner/app/src/components/Modal.tsx
Normal file
19
lama_cleaner/app/src/components/Modal.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
38
lama_cleaner/app/src/components/Slider.tsx
Normal file
38
lama_cleaner/app/src/components/Slider.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
6
lama_cleaner/app/src/index.tsx
Normal file
6
lama_cleaner/app/src/index.tsx
Normal 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'))
|
1
lama_cleaner/app/src/react-app-env.d.ts
vendored
Normal file
1
lama_cleaner/app/src/react-app-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/// <reference types="react-scripts" />
|
5
lama_cleaner/app/src/setupTests.ts
Normal file
5
lama_cleaner/app/src/setupTests.ts
Normal 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'
|
46
lama_cleaner/app/src/styles/tailwind.css
Normal file
46
lama_cleaner/app/src/styles/tailwind.css
Normal 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;*/
|
||||||
|
}
|
||||||
|
}
|
158
lama_cleaner/app/src/utils.ts
Normal file
158
lama_cleaner/app/src/utils.ts
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
37
lama_cleaner/app/tailwind.config.js
Normal file
37
lama_cleaner/app/tailwind.config.js
Normal 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: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
26
lama_cleaner/app/tsconfig.json
Normal file
26
lama_cleaner/app/tsconfig.json
Normal 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
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
65
lama_cleaner/helper.py
Normal 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
100
main.py
Normal 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
4
requirements.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
torch>=1.8.2
|
||||||
|
opencv-python
|
||||||
|
flask_cors
|
||||||
|
flask
|
Loading…
Reference in New Issue
Block a user