Spaces:
Running
Running
zhou20120904
commited on
Commit
•
a62d4c5
1
Parent(s):
5d78f81
Upload 48 files
Browse files- .gitattributes +2 -0
- Dockerfile +17 -0
- LICENSE +674 -0
- index.html +19 -0
- media/bar.png +0 -0
- media/cover.png +3 -0
- media/github.mp4 +3 -0
- media/super-resolution.jpg +0 -0
- media/wechat.jpg +0 -0
- media/wechatG.jpg +0 -0
- messages/en.json +14 -0
- messages/zh.json +14 -0
- package-lock.json +0 -0
- package.json +85 -0
- postcss.config.js +6 -0
- project.inlang/project_id +1 -0
- project.inlang/settings.json +19 -0
- public/_headers +3 -0
- public/examples/bag.jpeg +0 -0
- public/examples/bird.jpeg +0 -0
- public/examples/car.jpeg +0 -0
- public/examples/dog.jpeg +0 -0
- public/examples/jacket.jpeg +0 -0
- public/examples/paris.jpeg +0 -0
- public/examples/shoe.jpeg +0 -0
- public/examples/table.jpeg +0 -0
- src/App.tsx +185 -0
- src/Editor.tsx +707 -0
- src/adapters/cache.ts +131 -0
- src/adapters/inpainting.ts +271 -0
- src/adapters/superResolution.ts +297 -0
- src/adapters/util.ts +78 -0
- src/components/Button.tsx +75 -0
- src/components/FileSelect.tsx +135 -0
- src/components/Link.tsx +13 -0
- src/components/Logo.tsx +48 -0
- src/components/MadeWidthBadge.tsx +72 -0
- src/components/Modal.tsx +19 -0
- src/components/Progress.tsx +17 -0
- src/components/Slider.tsx +34 -0
- src/index.css +3 -0
- src/index.tsx +8 -0
- src/react-app-env.d.ts +1 -0
- src/setupTests.ts +5 -0
- src/utils.ts +159 -0
- src/vite-env.d.ts +1 -0
- tailwind.config.js +18 -0
- tsconfig.json +21 -0
- vite.config.ts +27 -0
.gitattributes
CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
36 |
+
media/cover.png filter=lfs diff=lfs merge=lfs -text
|
37 |
+
media/github.mp4 filter=lfs diff=lfs merge=lfs -text
|
Dockerfile
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM node:18 as builder
|
2 |
+
|
3 |
+
WORKDIR /usr/src/app
|
4 |
+
|
5 |
+
# Copy the package.json and package-lock.json files over
|
6 |
+
# We do this FIRST so that we don't copy the huge node_modules folder over from our local machine
|
7 |
+
# The node_modules can contain machine-specific libraries, so it should be created by the machine that's actually running the code
|
8 |
+
COPY . ./
|
9 |
+
|
10 |
+
# Now we run NPM install, which includes dev dependencies
|
11 |
+
RUN npm install
|
12 |
+
|
13 |
+
FROM alpine:latest as production
|
14 |
+
RUN apk --no-cache add nodejs ca-certificates
|
15 |
+
WORKDIR /root/
|
16 |
+
COPY --from=builder /usr/src/app ./
|
17 |
+
CMD [ "node", "node_modules/vite/bin/vite.js", "--host" ]
|
LICENSE
ADDED
@@ -0,0 +1,674 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
GNU GENERAL PUBLIC LICENSE
|
2 |
+
Version 3, 29 June 2007
|
3 |
+
|
4 |
+
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
5 |
+
Everyone is permitted to copy and distribute verbatim copies
|
6 |
+
of this license document, but changing it is not allowed.
|
7 |
+
|
8 |
+
Preamble
|
9 |
+
|
10 |
+
The GNU General Public License is a free, copyleft license for
|
11 |
+
software and other kinds of works.
|
12 |
+
|
13 |
+
The licenses for most software and other practical works are designed
|
14 |
+
to take away your freedom to share and change the works. By contrast,
|
15 |
+
the GNU General Public License is intended to guarantee your freedom to
|
16 |
+
share and change all versions of a program--to make sure it remains free
|
17 |
+
software for all its users. We, the Free Software Foundation, use the
|
18 |
+
GNU General Public License for most of our software; it applies also to
|
19 |
+
any other work released this way by its authors. You can apply it to
|
20 |
+
your programs, too.
|
21 |
+
|
22 |
+
When we speak of free software, we are referring to freedom, not
|
23 |
+
price. Our General Public Licenses are designed to make sure that you
|
24 |
+
have the freedom to distribute copies of free software (and charge for
|
25 |
+
them if you wish), that you receive source code or can get it if you
|
26 |
+
want it, that you can change the software or use pieces of it in new
|
27 |
+
free programs, and that you know you can do these things.
|
28 |
+
|
29 |
+
To protect your rights, we need to prevent others from denying you
|
30 |
+
these rights or asking you to surrender the rights. Therefore, you have
|
31 |
+
certain responsibilities if you distribute copies of the software, or if
|
32 |
+
you modify it: responsibilities to respect the freedom of others.
|
33 |
+
|
34 |
+
For example, if you distribute copies of such a program, whether
|
35 |
+
gratis or for a fee, you must pass on to the recipients the same
|
36 |
+
freedoms that you received. You must make sure that they, too, receive
|
37 |
+
or can get the source code. And you must show them these terms so they
|
38 |
+
know their rights.
|
39 |
+
|
40 |
+
Developers that use the GNU GPL protect your rights with two steps:
|
41 |
+
(1) assert copyright on the software, and (2) offer you this License
|
42 |
+
giving you legal permission to copy, distribute and/or modify it.
|
43 |
+
|
44 |
+
For the developers' and authors' protection, the GPL clearly explains
|
45 |
+
that there is no warranty for this free software. For both users' and
|
46 |
+
authors' sake, the GPL requires that modified versions be marked as
|
47 |
+
changed, so that their problems will not be attributed erroneously to
|
48 |
+
authors of previous versions.
|
49 |
+
|
50 |
+
Some devices are designed to deny users access to install or run
|
51 |
+
modified versions of the software inside them, although the manufacturer
|
52 |
+
can do so. This is fundamentally incompatible with the aim of
|
53 |
+
protecting users' freedom to change the software. The systematic
|
54 |
+
pattern of such abuse occurs in the area of products for individuals to
|
55 |
+
use, which is precisely where it is most unacceptable. Therefore, we
|
56 |
+
have designed this version of the GPL to prohibit the practice for those
|
57 |
+
products. If such problems arise substantially in other domains, we
|
58 |
+
stand ready to extend this provision to those domains in future versions
|
59 |
+
of the GPL, as needed to protect the freedom of users.
|
60 |
+
|
61 |
+
Finally, every program is threatened constantly by software patents.
|
62 |
+
States should not allow patents to restrict development and use of
|
63 |
+
software on general-purpose computers, but in those that do, we wish to
|
64 |
+
avoid the special danger that patents applied to a free program could
|
65 |
+
make it effectively proprietary. To prevent this, the GPL assures that
|
66 |
+
patents cannot be used to render the program non-free.
|
67 |
+
|
68 |
+
The precise terms and conditions for copying, distribution and
|
69 |
+
modification follow.
|
70 |
+
|
71 |
+
TERMS AND CONDITIONS
|
72 |
+
|
73 |
+
0. Definitions.
|
74 |
+
|
75 |
+
"This License" refers to version 3 of the GNU General Public License.
|
76 |
+
|
77 |
+
"Copyright" also means copyright-like laws that apply to other kinds of
|
78 |
+
works, such as semiconductor masks.
|
79 |
+
|
80 |
+
"The Program" refers to any copyrightable work licensed under this
|
81 |
+
License. Each licensee is addressed as "you". "Licensees" and
|
82 |
+
"recipients" may be individuals or organizations.
|
83 |
+
|
84 |
+
To "modify" a work means to copy from or adapt all or part of the work
|
85 |
+
in a fashion requiring copyright permission, other than the making of an
|
86 |
+
exact copy. The resulting work is called a "modified version" of the
|
87 |
+
earlier work or a work "based on" the earlier work.
|
88 |
+
|
89 |
+
A "covered work" means either the unmodified Program or a work based
|
90 |
+
on the Program.
|
91 |
+
|
92 |
+
To "propagate" a work means to do anything with it that, without
|
93 |
+
permission, would make you directly or secondarily liable for
|
94 |
+
infringement under applicable copyright law, except executing it on a
|
95 |
+
computer or modifying a private copy. Propagation includes copying,
|
96 |
+
distribution (with or without modification), making available to the
|
97 |
+
public, and in some countries other activities as well.
|
98 |
+
|
99 |
+
To "convey" a work means any kind of propagation that enables other
|
100 |
+
parties to make or receive copies. Mere interaction with a user through
|
101 |
+
a computer network, with no transfer of a copy, is not conveying.
|
102 |
+
|
103 |
+
An interactive user interface displays "Appropriate Legal Notices"
|
104 |
+
to the extent that it includes a convenient and prominently visible
|
105 |
+
feature that (1) displays an appropriate copyright notice, and (2)
|
106 |
+
tells the user that there is no warranty for the work (except to the
|
107 |
+
extent that warranties are provided), that licensees may convey the
|
108 |
+
work under this License, and how to view a copy of this License. If
|
109 |
+
the interface presents a list of user commands or options, such as a
|
110 |
+
menu, a prominent item in the list meets this criterion.
|
111 |
+
|
112 |
+
1. Source Code.
|
113 |
+
|
114 |
+
The "source code" for a work means the preferred form of the work
|
115 |
+
for making modifications to it. "Object code" means any non-source
|
116 |
+
form of a work.
|
117 |
+
|
118 |
+
A "Standard Interface" means an interface that either is an official
|
119 |
+
standard defined by a recognized standards body, or, in the case of
|
120 |
+
interfaces specified for a particular programming language, one that
|
121 |
+
is widely used among developers working in that language.
|
122 |
+
|
123 |
+
The "System Libraries" of an executable work include anything, other
|
124 |
+
than the work as a whole, that (a) is included in the normal form of
|
125 |
+
packaging a Major Component, but which is not part of that Major
|
126 |
+
Component, and (b) serves only to enable use of the work with that
|
127 |
+
Major Component, or to implement a Standard Interface for which an
|
128 |
+
implementation is available to the public in source code form. A
|
129 |
+
"Major Component", in this context, means a major essential component
|
130 |
+
(kernel, window system, and so on) of the specific operating system
|
131 |
+
(if any) on which the executable work runs, or a compiler used to
|
132 |
+
produce the work, or an object code interpreter used to run it.
|
133 |
+
|
134 |
+
The "Corresponding Source" for a work in object code form means all
|
135 |
+
the source code needed to generate, install, and (for an executable
|
136 |
+
work) run the object code and to modify the work, including scripts to
|
137 |
+
control those activities. However, it does not include the work's
|
138 |
+
System Libraries, or general-purpose tools or generally available free
|
139 |
+
programs which are used unmodified in performing those activities but
|
140 |
+
which are not part of the work. For example, Corresponding Source
|
141 |
+
includes interface definition files associated with source files for
|
142 |
+
the work, and the source code for shared libraries and dynamically
|
143 |
+
linked subprograms that the work is specifically designed to require,
|
144 |
+
such as by intimate data communication or control flow between those
|
145 |
+
subprograms and other parts of the work.
|
146 |
+
|
147 |
+
The Corresponding Source need not include anything that users
|
148 |
+
can regenerate automatically from other parts of the Corresponding
|
149 |
+
Source.
|
150 |
+
|
151 |
+
The Corresponding Source for a work in source code form is that
|
152 |
+
same work.
|
153 |
+
|
154 |
+
2. Basic Permissions.
|
155 |
+
|
156 |
+
All rights granted under this License are granted for the term of
|
157 |
+
copyright on the Program, and are irrevocable provided the stated
|
158 |
+
conditions are met. This License explicitly affirms your unlimited
|
159 |
+
permission to run the unmodified Program. The output from running a
|
160 |
+
covered work is covered by this License only if the output, given its
|
161 |
+
content, constitutes a covered work. This License acknowledges your
|
162 |
+
rights of fair use or other equivalent, as provided by copyright law.
|
163 |
+
|
164 |
+
You may make, run and propagate covered works that you do not
|
165 |
+
convey, without conditions so long as your license otherwise remains
|
166 |
+
in force. You may convey covered works to others for the sole purpose
|
167 |
+
of having them make modifications exclusively for you, or provide you
|
168 |
+
with facilities for running those works, provided that you comply with
|
169 |
+
the terms of this License in conveying all material for which you do
|
170 |
+
not control copyright. Those thus making or running the covered works
|
171 |
+
for you must do so exclusively on your behalf, under your direction
|
172 |
+
and control, on terms that prohibit them from making any copies of
|
173 |
+
your copyrighted material outside their relationship with you.
|
174 |
+
|
175 |
+
Conveying under any other circumstances is permitted solely under
|
176 |
+
the conditions stated below. Sublicensing is not allowed; section 10
|
177 |
+
makes it unnecessary.
|
178 |
+
|
179 |
+
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
180 |
+
|
181 |
+
No covered work shall be deemed part of an effective technological
|
182 |
+
measure under any applicable law fulfilling obligations under article
|
183 |
+
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
184 |
+
similar laws prohibiting or restricting circumvention of such
|
185 |
+
measures.
|
186 |
+
|
187 |
+
When you convey a covered work, you waive any legal power to forbid
|
188 |
+
circumvention of technological measures to the extent such circumvention
|
189 |
+
is effected by exercising rights under this License with respect to
|
190 |
+
the covered work, and you disclaim any intention to limit operation or
|
191 |
+
modification of the work as a means of enforcing, against the work's
|
192 |
+
users, your or third parties' legal rights to forbid circumvention of
|
193 |
+
technological measures.
|
194 |
+
|
195 |
+
4. Conveying Verbatim Copies.
|
196 |
+
|
197 |
+
You may convey verbatim copies of the Program's source code as you
|
198 |
+
receive it, in any medium, provided that you conspicuously and
|
199 |
+
appropriately publish on each copy an appropriate copyright notice;
|
200 |
+
keep intact all notices stating that this License and any
|
201 |
+
non-permissive terms added in accord with section 7 apply to the code;
|
202 |
+
keep intact all notices of the absence of any warranty; and give all
|
203 |
+
recipients a copy of this License along with the Program.
|
204 |
+
|
205 |
+
You may charge any price or no price for each copy that you convey,
|
206 |
+
and you may offer support or warranty protection for a fee.
|
207 |
+
|
208 |
+
5. Conveying Modified Source Versions.
|
209 |
+
|
210 |
+
You may convey a work based on the Program, or the modifications to
|
211 |
+
produce it from the Program, in the form of source code under the
|
212 |
+
terms of section 4, provided that you also meet all of these conditions:
|
213 |
+
|
214 |
+
a) The work must carry prominent notices stating that you modified
|
215 |
+
it, and giving a relevant date.
|
216 |
+
|
217 |
+
b) The work must carry prominent notices stating that it is
|
218 |
+
released under this License and any conditions added under section
|
219 |
+
7. This requirement modifies the requirement in section 4 to
|
220 |
+
"keep intact all notices".
|
221 |
+
|
222 |
+
c) You must license the entire work, as a whole, under this
|
223 |
+
License to anyone who comes into possession of a copy. This
|
224 |
+
License will therefore apply, along with any applicable section 7
|
225 |
+
additional terms, to the whole of the work, and all its parts,
|
226 |
+
regardless of how they are packaged. This License gives no
|
227 |
+
permission to license the work in any other way, but it does not
|
228 |
+
invalidate such permission if you have separately received it.
|
229 |
+
|
230 |
+
d) If the work has interactive user interfaces, each must display
|
231 |
+
Appropriate Legal Notices; however, if the Program has interactive
|
232 |
+
interfaces that do not display Appropriate Legal Notices, your
|
233 |
+
work need not make them do so.
|
234 |
+
|
235 |
+
A compilation of a covered work with other separate and independent
|
236 |
+
works, which are not by their nature extensions of the covered work,
|
237 |
+
and which are not combined with it such as to form a larger program,
|
238 |
+
in or on a volume of a storage or distribution medium, is called an
|
239 |
+
"aggregate" if the compilation and its resulting copyright are not
|
240 |
+
used to limit the access or legal rights of the compilation's users
|
241 |
+
beyond what the individual works permit. Inclusion of a covered work
|
242 |
+
in an aggregate does not cause this License to apply to the other
|
243 |
+
parts of the aggregate.
|
244 |
+
|
245 |
+
6. Conveying Non-Source Forms.
|
246 |
+
|
247 |
+
You may convey a covered work in object code form under the terms
|
248 |
+
of sections 4 and 5, provided that you also convey the
|
249 |
+
machine-readable Corresponding Source under the terms of this License,
|
250 |
+
in one of these ways:
|
251 |
+
|
252 |
+
a) Convey the object code in, or embodied in, a physical product
|
253 |
+
(including a physical distribution medium), accompanied by the
|
254 |
+
Corresponding Source fixed on a durable physical medium
|
255 |
+
customarily used for software interchange.
|
256 |
+
|
257 |
+
b) Convey the object code in, or embodied in, a physical product
|
258 |
+
(including a physical distribution medium), accompanied by a
|
259 |
+
written offer, valid for at least three years and valid for as
|
260 |
+
long as you offer spare parts or customer support for that product
|
261 |
+
model, to give anyone who possesses the object code either (1) a
|
262 |
+
copy of the Corresponding Source for all the software in the
|
263 |
+
product that is covered by this License, on a durable physical
|
264 |
+
medium customarily used for software interchange, for a price no
|
265 |
+
more than your reasonable cost of physically performing this
|
266 |
+
conveying of source, or (2) access to copy the
|
267 |
+
Corresponding Source from a network server at no charge.
|
268 |
+
|
269 |
+
c) Convey individual copies of the object code with a copy of the
|
270 |
+
written offer to provide the Corresponding Source. This
|
271 |
+
alternative is allowed only occasionally and noncommercially, and
|
272 |
+
only if you received the object code with such an offer, in accord
|
273 |
+
with subsection 6b.
|
274 |
+
|
275 |
+
d) Convey the object code by offering access from a designated
|
276 |
+
place (gratis or for a charge), and offer equivalent access to the
|
277 |
+
Corresponding Source in the same way through the same place at no
|
278 |
+
further charge. You need not require recipients to copy the
|
279 |
+
Corresponding Source along with the object code. If the place to
|
280 |
+
copy the object code is a network server, the Corresponding Source
|
281 |
+
may be on a different server (operated by you or a third party)
|
282 |
+
that supports equivalent copying facilities, provided you maintain
|
283 |
+
clear directions next to the object code saying where to find the
|
284 |
+
Corresponding Source. Regardless of what server hosts the
|
285 |
+
Corresponding Source, you remain obligated to ensure that it is
|
286 |
+
available for as long as needed to satisfy these requirements.
|
287 |
+
|
288 |
+
e) Convey the object code using peer-to-peer transmission, provided
|
289 |
+
you inform other peers where the object code and Corresponding
|
290 |
+
Source of the work are being offered to the general public at no
|
291 |
+
charge under subsection 6d.
|
292 |
+
|
293 |
+
A separable portion of the object code, whose source code is excluded
|
294 |
+
from the Corresponding Source as a System Library, need not be
|
295 |
+
included in conveying the object code work.
|
296 |
+
|
297 |
+
A "User Product" is either (1) a "consumer product", which means any
|
298 |
+
tangible personal property which is normally used for personal, family,
|
299 |
+
or household purposes, or (2) anything designed or sold for incorporation
|
300 |
+
into a dwelling. In determining whether a product is a consumer product,
|
301 |
+
doubtful cases shall be resolved in favor of coverage. For a particular
|
302 |
+
product received by a particular user, "normally used" refers to a
|
303 |
+
typical or common use of that class of product, regardless of the status
|
304 |
+
of the particular user or of the way in which the particular user
|
305 |
+
actually uses, or expects or is expected to use, the product. A product
|
306 |
+
is a consumer product regardless of whether the product has substantial
|
307 |
+
commercial, industrial or non-consumer uses, unless such uses represent
|
308 |
+
the only significant mode of use of the product.
|
309 |
+
|
310 |
+
"Installation Information" for a User Product means any methods,
|
311 |
+
procedures, authorization keys, or other information required to install
|
312 |
+
and execute modified versions of a covered work in that User Product from
|
313 |
+
a modified version of its Corresponding Source. The information must
|
314 |
+
suffice to ensure that the continued functioning of the modified object
|
315 |
+
code is in no case prevented or interfered with solely because
|
316 |
+
modification has been made.
|
317 |
+
|
318 |
+
If you convey an object code work under this section in, or with, or
|
319 |
+
specifically for use in, a User Product, and the conveying occurs as
|
320 |
+
part of a transaction in which the right of possession and use of the
|
321 |
+
User Product is transferred to the recipient in perpetuity or for a
|
322 |
+
fixed term (regardless of how the transaction is characterized), the
|
323 |
+
Corresponding Source conveyed under this section must be accompanied
|
324 |
+
by the Installation Information. But this requirement does not apply
|
325 |
+
if neither you nor any third party retains the ability to install
|
326 |
+
modified object code on the User Product (for example, the work has
|
327 |
+
been installed in ROM).
|
328 |
+
|
329 |
+
The requirement to provide Installation Information does not include a
|
330 |
+
requirement to continue to provide support service, warranty, or updates
|
331 |
+
for a work that has been modified or installed by the recipient, or for
|
332 |
+
the User Product in which it has been modified or installed. Access to a
|
333 |
+
network may be denied when the modification itself materially and
|
334 |
+
adversely affects the operation of the network or violates the rules and
|
335 |
+
protocols for communication across the network.
|
336 |
+
|
337 |
+
Corresponding Source conveyed, and Installation Information provided,
|
338 |
+
in accord with this section must be in a format that is publicly
|
339 |
+
documented (and with an implementation available to the public in
|
340 |
+
source code form), and must require no special password or key for
|
341 |
+
unpacking, reading or copying.
|
342 |
+
|
343 |
+
7. Additional Terms.
|
344 |
+
|
345 |
+
"Additional permissions" are terms that supplement the terms of this
|
346 |
+
License by making exceptions from one or more of its conditions.
|
347 |
+
Additional permissions that are applicable to the entire Program shall
|
348 |
+
be treated as though they were included in this License, to the extent
|
349 |
+
that they are valid under applicable law. If additional permissions
|
350 |
+
apply only to part of the Program, that part may be used separately
|
351 |
+
under those permissions, but the entire Program remains governed by
|
352 |
+
this License without regard to the additional permissions.
|
353 |
+
|
354 |
+
When you convey a copy of a covered work, you may at your option
|
355 |
+
remove any additional permissions from that copy, or from any part of
|
356 |
+
it. (Additional permissions may be written to require their own
|
357 |
+
removal in certain cases when you modify the work.) You may place
|
358 |
+
additional permissions on material, added by you to a covered work,
|
359 |
+
for which you have or can give appropriate copyright permission.
|
360 |
+
|
361 |
+
Notwithstanding any other provision of this License, for material you
|
362 |
+
add to a covered work, you may (if authorized by the copyright holders of
|
363 |
+
that material) supplement the terms of this License with terms:
|
364 |
+
|
365 |
+
a) Disclaiming warranty or limiting liability differently from the
|
366 |
+
terms of sections 15 and 16 of this License; or
|
367 |
+
|
368 |
+
b) Requiring preservation of specified reasonable legal notices or
|
369 |
+
author attributions in that material or in the Appropriate Legal
|
370 |
+
Notices displayed by works containing it; or
|
371 |
+
|
372 |
+
c) Prohibiting misrepresentation of the origin of that material, or
|
373 |
+
requiring that modified versions of such material be marked in
|
374 |
+
reasonable ways as different from the original version; or
|
375 |
+
|
376 |
+
d) Limiting the use for publicity purposes of names of licensors or
|
377 |
+
authors of the material; or
|
378 |
+
|
379 |
+
e) Declining to grant rights under trademark law for use of some
|
380 |
+
trade names, trademarks, or service marks; or
|
381 |
+
|
382 |
+
f) Requiring indemnification of licensors and authors of that
|
383 |
+
material by anyone who conveys the material (or modified versions of
|
384 |
+
it) with contractual assumptions of liability to the recipient, for
|
385 |
+
any liability that these contractual assumptions directly impose on
|
386 |
+
those licensors and authors.
|
387 |
+
|
388 |
+
All other non-permissive additional terms are considered "further
|
389 |
+
restrictions" within the meaning of section 10. If the Program as you
|
390 |
+
received it, or any part of it, contains a notice stating that it is
|
391 |
+
governed by this License along with a term that is a further
|
392 |
+
restriction, you may remove that term. If a license document contains
|
393 |
+
a further restriction but permits relicensing or conveying under this
|
394 |
+
License, you may add to a covered work material governed by the terms
|
395 |
+
of that license document, provided that the further restriction does
|
396 |
+
not survive such relicensing or conveying.
|
397 |
+
|
398 |
+
If you add terms to a covered work in accord with this section, you
|
399 |
+
must place, in the relevant source files, a statement of the
|
400 |
+
additional terms that apply to those files, or a notice indicating
|
401 |
+
where to find the applicable terms.
|
402 |
+
|
403 |
+
Additional terms, permissive or non-permissive, may be stated in the
|
404 |
+
form of a separately written license, or stated as exceptions;
|
405 |
+
the above requirements apply either way.
|
406 |
+
|
407 |
+
8. Termination.
|
408 |
+
|
409 |
+
You may not propagate or modify a covered work except as expressly
|
410 |
+
provided under this License. Any attempt otherwise to propagate or
|
411 |
+
modify it is void, and will automatically terminate your rights under
|
412 |
+
this License (including any patent licenses granted under the third
|
413 |
+
paragraph of section 11).
|
414 |
+
|
415 |
+
However, if you cease all violation of this License, then your
|
416 |
+
license from a particular copyright holder is reinstated (a)
|
417 |
+
provisionally, unless and until the copyright holder explicitly and
|
418 |
+
finally terminates your license, and (b) permanently, if the copyright
|
419 |
+
holder fails to notify you of the violation by some reasonable means
|
420 |
+
prior to 60 days after the cessation.
|
421 |
+
|
422 |
+
Moreover, your license from a particular copyright holder is
|
423 |
+
reinstated permanently if the copyright holder notifies you of the
|
424 |
+
violation by some reasonable means, this is the first time you have
|
425 |
+
received notice of violation of this License (for any work) from that
|
426 |
+
copyright holder, and you cure the violation prior to 30 days after
|
427 |
+
your receipt of the notice.
|
428 |
+
|
429 |
+
Termination of your rights under this section does not terminate the
|
430 |
+
licenses of parties who have received copies or rights from you under
|
431 |
+
this License. If your rights have been terminated and not permanently
|
432 |
+
reinstated, you do not qualify to receive new licenses for the same
|
433 |
+
material under section 10.
|
434 |
+
|
435 |
+
9. Acceptance Not Required for Having Copies.
|
436 |
+
|
437 |
+
You are not required to accept this License in order to receive or
|
438 |
+
run a copy of the Program. Ancillary propagation of a covered work
|
439 |
+
occurring solely as a consequence of using peer-to-peer transmission
|
440 |
+
to receive a copy likewise does not require acceptance. However,
|
441 |
+
nothing other than this License grants you permission to propagate or
|
442 |
+
modify any covered work. These actions infringe copyright if you do
|
443 |
+
not accept this License. Therefore, by modifying or propagating a
|
444 |
+
covered work, you indicate your acceptance of this License to do so.
|
445 |
+
|
446 |
+
10. Automatic Licensing of Downstream Recipients.
|
447 |
+
|
448 |
+
Each time you convey a covered work, the recipient automatically
|
449 |
+
receives a license from the original licensors, to run, modify and
|
450 |
+
propagate that work, subject to this License. You are not responsible
|
451 |
+
for enforcing compliance by third parties with this License.
|
452 |
+
|
453 |
+
An "entity transaction" is a transaction transferring control of an
|
454 |
+
organization, or substantially all assets of one, or subdividing an
|
455 |
+
organization, or merging organizations. If propagation of a covered
|
456 |
+
work results from an entity transaction, each party to that
|
457 |
+
transaction who receives a copy of the work also receives whatever
|
458 |
+
licenses to the work the party's predecessor in interest had or could
|
459 |
+
give under the previous paragraph, plus a right to possession of the
|
460 |
+
Corresponding Source of the work from the predecessor in interest, if
|
461 |
+
the predecessor has it or can get it with reasonable efforts.
|
462 |
+
|
463 |
+
You may not impose any further restrictions on the exercise of the
|
464 |
+
rights granted or affirmed under this License. For example, you may
|
465 |
+
not impose a license fee, royalty, or other charge for exercise of
|
466 |
+
rights granted under this License, and you may not initiate litigation
|
467 |
+
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
468 |
+
any patent claim is infringed by making, using, selling, offering for
|
469 |
+
sale, or importing the Program or any portion of it.
|
470 |
+
|
471 |
+
11. Patents.
|
472 |
+
|
473 |
+
A "contributor" is a copyright holder who authorizes use under this
|
474 |
+
License of the Program or a work on which the Program is based. The
|
475 |
+
work thus licensed is called the contributor's "contributor version".
|
476 |
+
|
477 |
+
A contributor's "essential patent claims" are all patent claims
|
478 |
+
owned or controlled by the contributor, whether already acquired or
|
479 |
+
hereafter acquired, that would be infringed by some manner, permitted
|
480 |
+
by this License, of making, using, or selling its contributor version,
|
481 |
+
but do not include claims that would be infringed only as a
|
482 |
+
consequence of further modification of the contributor version. For
|
483 |
+
purposes of this definition, "control" includes the right to grant
|
484 |
+
patent sublicenses in a manner consistent with the requirements of
|
485 |
+
this License.
|
486 |
+
|
487 |
+
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
488 |
+
patent license under the contributor's essential patent claims, to
|
489 |
+
make, use, sell, offer for sale, import and otherwise run, modify and
|
490 |
+
propagate the contents of its contributor version.
|
491 |
+
|
492 |
+
In the following three paragraphs, a "patent license" is any express
|
493 |
+
agreement or commitment, however denominated, not to enforce a patent
|
494 |
+
(such as an express permission to practice a patent or covenant not to
|
495 |
+
sue for patent infringement). To "grant" such a patent license to a
|
496 |
+
party means to make such an agreement or commitment not to enforce a
|
497 |
+
patent against the party.
|
498 |
+
|
499 |
+
If you convey a covered work, knowingly relying on a patent license,
|
500 |
+
and the Corresponding Source of the work is not available for anyone
|
501 |
+
to copy, free of charge and under the terms of this License, through a
|
502 |
+
publicly available network server or other readily accessible means,
|
503 |
+
then you must either (1) cause the Corresponding Source to be so
|
504 |
+
available, or (2) arrange to deprive yourself of the benefit of the
|
505 |
+
patent license for this particular work, or (3) arrange, in a manner
|
506 |
+
consistent with the requirements of this License, to extend the patent
|
507 |
+
license to downstream recipients. "Knowingly relying" means you have
|
508 |
+
actual knowledge that, but for the patent license, your conveying the
|
509 |
+
covered work in a country, or your recipient's use of the covered work
|
510 |
+
in a country, would infringe one or more identifiable patents in that
|
511 |
+
country that you have reason to believe are valid.
|
512 |
+
|
513 |
+
If, pursuant to or in connection with a single transaction or
|
514 |
+
arrangement, you convey, or propagate by procuring conveyance of, a
|
515 |
+
covered work, and grant a patent license to some of the parties
|
516 |
+
receiving the covered work authorizing them to use, propagate, modify
|
517 |
+
or convey a specific copy of the covered work, then the patent license
|
518 |
+
you grant is automatically extended to all recipients of the covered
|
519 |
+
work and works based on it.
|
520 |
+
|
521 |
+
A patent license is "discriminatory" if it does not include within
|
522 |
+
the scope of its coverage, prohibits the exercise of, or is
|
523 |
+
conditioned on the non-exercise of one or more of the rights that are
|
524 |
+
specifically granted under this License. You may not convey a covered
|
525 |
+
work if you are a party to an arrangement with a third party that is
|
526 |
+
in the business of distributing software, under which you make payment
|
527 |
+
to the third party based on the extent of your activity of conveying
|
528 |
+
the work, and under which the third party grants, to any of the
|
529 |
+
parties who would receive the covered work from you, a discriminatory
|
530 |
+
patent license (a) in connection with copies of the covered work
|
531 |
+
conveyed by you (or copies made from those copies), or (b) primarily
|
532 |
+
for and in connection with specific products or compilations that
|
533 |
+
contain the covered work, unless you entered into that arrangement,
|
534 |
+
or that patent license was granted, prior to 28 March 2007.
|
535 |
+
|
536 |
+
Nothing in this License shall be construed as excluding or limiting
|
537 |
+
any implied license or other defenses to infringement that may
|
538 |
+
otherwise be available to you under applicable patent law.
|
539 |
+
|
540 |
+
12. No Surrender of Others' Freedom.
|
541 |
+
|
542 |
+
If conditions are imposed on you (whether by court order, agreement or
|
543 |
+
otherwise) that contradict the conditions of this License, they do not
|
544 |
+
excuse you from the conditions of this License. If you cannot convey a
|
545 |
+
covered work so as to satisfy simultaneously your obligations under this
|
546 |
+
License and any other pertinent obligations, then as a consequence you may
|
547 |
+
not convey it at all. For example, if you agree to terms that obligate you
|
548 |
+
to collect a royalty for further conveying from those to whom you convey
|
549 |
+
the Program, the only way you could satisfy both those terms and this
|
550 |
+
License would be to refrain entirely from conveying the Program.
|
551 |
+
|
552 |
+
13. Use with the GNU Affero General Public License.
|
553 |
+
|
554 |
+
Notwithstanding any other provision of this License, you have
|
555 |
+
permission to link or combine any covered work with a work licensed
|
556 |
+
under version 3 of the GNU Affero General Public License into a single
|
557 |
+
combined work, and to convey the resulting work. The terms of this
|
558 |
+
License will continue to apply to the part which is the covered work,
|
559 |
+
but the special requirements of the GNU Affero General Public License,
|
560 |
+
section 13, concerning interaction through a network will apply to the
|
561 |
+
combination as such.
|
562 |
+
|
563 |
+
14. Revised Versions of this License.
|
564 |
+
|
565 |
+
The Free Software Foundation may publish revised and/or new versions of
|
566 |
+
the GNU General Public License from time to time. Such new versions will
|
567 |
+
be similar in spirit to the present version, but may differ in detail to
|
568 |
+
address new problems or concerns.
|
569 |
+
|
570 |
+
Each version is given a distinguishing version number. If the
|
571 |
+
Program specifies that a certain numbered version of the GNU General
|
572 |
+
Public License "or any later version" applies to it, you have the
|
573 |
+
option of following the terms and conditions either of that numbered
|
574 |
+
version or of any later version published by the Free Software
|
575 |
+
Foundation. If the Program does not specify a version number of the
|
576 |
+
GNU General Public License, you may choose any version ever published
|
577 |
+
by the Free Software Foundation.
|
578 |
+
|
579 |
+
If the Program specifies that a proxy can decide which future
|
580 |
+
versions of the GNU General Public License can be used, that proxy's
|
581 |
+
public statement of acceptance of a version permanently authorizes you
|
582 |
+
to choose that version for the Program.
|
583 |
+
|
584 |
+
Later license versions may give you additional or different
|
585 |
+
permissions. However, no additional obligations are imposed on any
|
586 |
+
author or copyright holder as a result of your choosing to follow a
|
587 |
+
later version.
|
588 |
+
|
589 |
+
15. Disclaimer of Warranty.
|
590 |
+
|
591 |
+
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
592 |
+
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
593 |
+
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
594 |
+
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
595 |
+
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
596 |
+
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
597 |
+
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
598 |
+
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
599 |
+
|
600 |
+
16. Limitation of Liability.
|
601 |
+
|
602 |
+
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
603 |
+
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
604 |
+
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
605 |
+
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
606 |
+
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
607 |
+
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
608 |
+
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
609 |
+
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
610 |
+
SUCH DAMAGES.
|
611 |
+
|
612 |
+
17. Interpretation of Sections 15 and 16.
|
613 |
+
|
614 |
+
If the disclaimer of warranty and limitation of liability provided
|
615 |
+
above cannot be given local legal effect according to their terms,
|
616 |
+
reviewing courts shall apply local law that most closely approximates
|
617 |
+
an absolute waiver of all civil liability in connection with the
|
618 |
+
Program, unless a warranty or assumption of liability accompanies a
|
619 |
+
copy of the Program in return for a fee.
|
620 |
+
|
621 |
+
END OF TERMS AND CONDITIONS
|
622 |
+
|
623 |
+
How to Apply These Terms to Your New Programs
|
624 |
+
|
625 |
+
If you develop a new program, and you want it to be of the greatest
|
626 |
+
possible use to the public, the best way to achieve this is to make it
|
627 |
+
free software which everyone can redistribute and change under these terms.
|
628 |
+
|
629 |
+
To do so, attach the following notices to the program. It is safest
|
630 |
+
to attach them to the start of each source file to most effectively
|
631 |
+
state the exclusion of warranty; and each file should have at least
|
632 |
+
the "copyright" line and a pointer to where the full notice is found.
|
633 |
+
|
634 |
+
<one line to give the program's name and a brief idea of what it does.>
|
635 |
+
Copyright (C) <year> <name of author>
|
636 |
+
|
637 |
+
This program is free software: you can redistribute it and/or modify
|
638 |
+
it under the terms of the GNU General Public License as published by
|
639 |
+
the Free Software Foundation, either version 3 of the License, or
|
640 |
+
(at your option) any later version.
|
641 |
+
|
642 |
+
This program is distributed in the hope that it will be useful,
|
643 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
644 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
645 |
+
GNU General Public License for more details.
|
646 |
+
|
647 |
+
You should have received a copy of the GNU General Public License
|
648 |
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
649 |
+
|
650 |
+
Also add information on how to contact you by electronic and paper mail.
|
651 |
+
|
652 |
+
If the program does terminal interaction, make it output a short
|
653 |
+
notice like this when it starts in an interactive mode:
|
654 |
+
|
655 |
+
<program> Copyright (C) <year> <name of author>
|
656 |
+
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
657 |
+
This is free software, and you are welcome to redistribute it
|
658 |
+
under certain conditions; type `show c' for details.
|
659 |
+
|
660 |
+
The hypothetical commands `show w' and `show c' should show the appropriate
|
661 |
+
parts of the General Public License. Of course, your program's commands
|
662 |
+
might be different; for a GUI interface, you would use an "about box".
|
663 |
+
|
664 |
+
You should also get your employer (if you work as a programmer) or school,
|
665 |
+
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
666 |
+
For more information on this, and how to apply and follow the GNU GPL, see
|
667 |
+
<https://www.gnu.org/licenses/>.
|
668 |
+
|
669 |
+
The GNU General Public License does not permit incorporating your program
|
670 |
+
into proprietary programs. If your program is a subroutine library, you
|
671 |
+
may consider it more useful to permit linking proprietary applications with
|
672 |
+
the library. If this is what you want to do, use the GNU Lesser General
|
673 |
+
Public License instead of this License. But first, please read
|
674 |
+
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
index.html
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="utf-8" />
|
5 |
+
<meta
|
6 |
+
name="viewport"
|
7 |
+
content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=0"
|
8 |
+
/>
|
9 |
+
<title>Inpaint-web</title>
|
10 |
+
|
11 |
+
</head>
|
12 |
+
<body class="h-screen">
|
13 |
+
<noscript>You need to enable JavaScript to run this app.</noscript>
|
14 |
+
<div id="root" class="h-full"></div>
|
15 |
+
|
16 |
+
<script type="module" src="/src/index.tsx"></script>
|
17 |
+
</body>
|
18 |
+
|
19 |
+
</html>
|
media/bar.png
ADDED
media/cover.png
ADDED
Git LFS Details
|
media/github.mp4
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:8753cc51e46d3198d9023f7b5f197b28708a9dd1d1eb73d6d8a1d7cf43c788c6
|
3 |
+
size 1657419
|
media/super-resolution.jpg
ADDED
media/wechat.jpg
ADDED
media/wechatG.jpg
ADDED
messages/en.json
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"$schema": "https://inlang.com/schema/inlang-message-format",
|
3 |
+
"drop_zone": "Click or drag here",
|
4 |
+
"try_it_images": "Try it:",
|
5 |
+
"feedback": "About me",
|
6 |
+
"start_new": "Start new",
|
7 |
+
"bruch_size": "Brush Size",
|
8 |
+
"original": "Original",
|
9 |
+
"upscale": "4x-upscaling",
|
10 |
+
"download": "Download",
|
11 |
+
"undo": "Undo",
|
12 |
+
"inpaint_model_download_message": "Need to download a 30MB model file, please wait patiently...",
|
13 |
+
"upscaleing_model_download_message": "Need to download a 70MB model file, please wait patiently..."
|
14 |
+
}
|
messages/zh.json
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"$schema": "https://inlang.com/schema/inlang-message-format",
|
3 |
+
"drop_zone": "点击 或者 拖拽到这里",
|
4 |
+
"try_it_images": "试一试:",
|
5 |
+
"feedback": "联系作者",
|
6 |
+
"start_new": "开始新的",
|
7 |
+
"bruch_size": "刷子大小",
|
8 |
+
"original": "原图",
|
9 |
+
"upscale": "4 倍放大",
|
10 |
+
"download": "下载",
|
11 |
+
"undo": "撤销",
|
12 |
+
"inpaint_model_download_message": "注意需要连接国际互联网,需要下载一次30MB大小模型文件,耐心等待。。。",
|
13 |
+
"upscaleing_model_download_message": "注意需要连接国际互联网,需要下载一次70MB大小模型文件,耐心等待。。。"
|
14 |
+
}
|
package-lock.json
ADDED
The diff for this file is too large to render.
See raw diff
|
|
package.json
ADDED
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"name": "inpaint-web",
|
3 |
+
"version": "0.1.0",
|
4 |
+
"private": true,
|
5 |
+
"dependencies": {
|
6 |
+
"@heroicons/react": "^1.0.4",
|
7 |
+
"@inlang/paraglide-js-adapter-vite": "^1.0.2",
|
8 |
+
"@vitejs/plugin-react-swc": "^3.5.0",
|
9 |
+
"localforage": "^1.10.0",
|
10 |
+
"opencv-ts": "^1.3.6",
|
11 |
+
"react": "^17.0.2",
|
12 |
+
"react-dom": "^17.0.2",
|
13 |
+
"react-use": "^17.3.1",
|
14 |
+
"react-zoom-pan-pinch": "^3.3.0",
|
15 |
+
"vite": "^5.0.2",
|
16 |
+
"vite-plugin-eslint": "^1.8.1",
|
17 |
+
"vite-plugin-svgr": "^4.2.0",
|
18 |
+
"vite-tsconfig-paths": "^4.2.1"
|
19 |
+
},
|
20 |
+
"scripts": {
|
21 |
+
"start": "vite --host",
|
22 |
+
"fast-build": "vite build",
|
23 |
+
"build": "tsc && vite build",
|
24 |
+
"serve": "vite preview",
|
25 |
+
"prepare": "husky install",
|
26 |
+
"format": "prettier --write .",
|
27 |
+
"postinstall": "paraglide-js compile --project ./project.inlang",
|
28 |
+
"paraglide": "paraglide-js compile --project ./project.inlang"
|
29 |
+
},
|
30 |
+
"lint-staged": {
|
31 |
+
"*.{vue,js,ts,jsx,tsx,css,sass,scss,md}": [
|
32 |
+
"prettier --write",
|
33 |
+
"echo '统一格式化完成🌸'"
|
34 |
+
]
|
35 |
+
},
|
36 |
+
"eslintConfig": {
|
37 |
+
"extends": "react-app"
|
38 |
+
},
|
39 |
+
"browserslist": {
|
40 |
+
"production": [
|
41 |
+
">0.2%",
|
42 |
+
"not dead",
|
43 |
+
"not op_mini all"
|
44 |
+
],
|
45 |
+
"development": [
|
46 |
+
"last 1 chrome version",
|
47 |
+
"last 1 firefox version",
|
48 |
+
"last 1 safari version"
|
49 |
+
]
|
50 |
+
},
|
51 |
+
"devDependencies": {
|
52 |
+
"@inlang/paraglide-js": "1.0.0-prerelease.19",
|
53 |
+
"@testing-library/jest-dom": "^5.14.1",
|
54 |
+
"@testing-library/react": "^12.1.2",
|
55 |
+
"@testing-library/user-event": "^13.5.0",
|
56 |
+
"@types/jest": "^27.0.2",
|
57 |
+
"@types/node": "^18.0.0",
|
58 |
+
"@types/react": "^17.0.30",
|
59 |
+
"@types/react-dom": "^17.0.9",
|
60 |
+
"@typescript-eslint/eslint-plugin": "^5.1.0",
|
61 |
+
"@vitest/coverage-v8": "^0.34.6",
|
62 |
+
"autoprefixer": "^10.4.16",
|
63 |
+
"cross-env": "7.x",
|
64 |
+
"delay-cli": "^1.1.0",
|
65 |
+
"eslint-config-airbnb": "^18.2.1",
|
66 |
+
"eslint-config-prettier": "^8.3.0",
|
67 |
+
"eslint-plugin-import": "^2.25.2",
|
68 |
+
"eslint-plugin-jsx-a11y": "^6.4.1",
|
69 |
+
"eslint-plugin-prettier": "^4.0.0",
|
70 |
+
"eslint-plugin-react": "^7.26.1",
|
71 |
+
"eslint-plugin-react-hooks": "^4.2.0",
|
72 |
+
"husky": "^8.0.3",
|
73 |
+
"jsdom": "^22.1.0",
|
74 |
+
"lint-staged": "^15.1.0",
|
75 |
+
"npm-run-all": "4.x",
|
76 |
+
"postcss": "^8.4.31",
|
77 |
+
"postcss-cli": "8.x",
|
78 |
+
"postcss-preset-env": "6.x",
|
79 |
+
"prettier": "^2.4.1",
|
80 |
+
"tailwind-scrollbar": "^2.0.0",
|
81 |
+
"tailwindcss": "^3.3.6",
|
82 |
+
"typescript": "4.x",
|
83 |
+
"vitest": "^0.34.6"
|
84 |
+
}
|
85 |
+
}
|
postcss.config.js
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
module.exports = {
|
2 |
+
plugins: {
|
3 |
+
tailwindcss: {},
|
4 |
+
autoprefixer: {},
|
5 |
+
},
|
6 |
+
}
|
project.inlang/project_id
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
ddd85f36c36577d96de7533e96ddc7706cbb8776bb4cf8218a033a5c7bcc21f2
|
project.inlang/settings.json
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"$schema": "https://inlang.com/schema/project-settings",
|
3 |
+
"sourceLanguageTag": "zh",
|
4 |
+
"languageTags": [
|
5 |
+
"en", "zh"
|
6 |
+
],
|
7 |
+
"modules": [
|
8 |
+
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-empty-pattern@latest/dist/index.js",
|
9 |
+
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-identical-pattern@latest/dist/index.js",
|
10 |
+
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-missing-translation@latest/dist/index.js",
|
11 |
+
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-without-source@latest/dist/index.js",
|
12 |
+
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-valid-js-identifier@latest/dist/index.js",
|
13 |
+
"https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@latest/dist/index.js",
|
14 |
+
"https://cdn.jsdelivr.net/npm/@inlang/plugin-m-function-matcher@latest/dist/index.js"
|
15 |
+
],
|
16 |
+
"plugin.inlang.messageFormat": {
|
17 |
+
"pathPattern": "./messages/{languageTag}.json"
|
18 |
+
}
|
19 |
+
}
|
public/_headers
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
/*
|
2 |
+
Cross-Origin-Opener-Policy: same-origin
|
3 |
+
Cross-Origin-Embedder-Policy: require-corp
|
public/examples/bag.jpeg
ADDED
public/examples/bird.jpeg
ADDED
public/examples/car.jpeg
ADDED
public/examples/dog.jpeg
ADDED
public/examples/jacket.jpeg
ADDED
public/examples/paris.jpeg
ADDED
public/examples/shoe.jpeg
ADDED
public/examples/table.jpeg
ADDED
src/App.tsx
ADDED
@@ -0,0 +1,185 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/* eslint-disable react-hooks/exhaustive-deps */
|
2 |
+
/* eslint-disable jsx-a11y/control-has-associated-label */
|
3 |
+
import { ArrowLeftIcon, InformationCircleIcon } from '@heroicons/react/outline'
|
4 |
+
import { useEffect, useRef, useState } from 'react'
|
5 |
+
import { useClickAway } from 'react-use'
|
6 |
+
import Button from './components/Button'
|
7 |
+
import FileSelect from './components/FileSelect'
|
8 |
+
import Modal from './components/Modal'
|
9 |
+
import Editor from './Editor'
|
10 |
+
import { resizeImageFile } from './utils'
|
11 |
+
import Progress from './components/Progress'
|
12 |
+
import { downloadModel } from './adapters/cache'
|
13 |
+
import * as m from './paraglide/messages'
|
14 |
+
import {
|
15 |
+
languageTag,
|
16 |
+
onSetLanguageTag,
|
17 |
+
setLanguageTag,
|
18 |
+
} from './paraglide/runtime'
|
19 |
+
|
20 |
+
function App() {
|
21 |
+
const [file, setFile] = useState<File>()
|
22 |
+
const [stateLanguageTag, setStateLanguageTag] = useState<'en' | 'zh'>('zh')
|
23 |
+
|
24 |
+
onSetLanguageTag(() => setStateLanguageTag(languageTag()))
|
25 |
+
|
26 |
+
const [showAbout, setShowAbout] = useState(false)
|
27 |
+
const modalRef = useRef(null)
|
28 |
+
|
29 |
+
const [downloadProgress, setDownloadProgress] = useState(100)
|
30 |
+
|
31 |
+
useEffect(() => {
|
32 |
+
downloadModel('inpaint', setDownloadProgress)
|
33 |
+
}, [])
|
34 |
+
|
35 |
+
useClickAway(modalRef, () => {
|
36 |
+
setShowAbout(false)
|
37 |
+
})
|
38 |
+
|
39 |
+
async function startWithDemoImage(img: string) {
|
40 |
+
const imgBlob = await fetch(`/examples/${img}.jpeg`).then(r => r.blob())
|
41 |
+
setFile(new File([imgBlob], `${img}.jpeg`, { type: 'image/jpeg' }))
|
42 |
+
}
|
43 |
+
|
44 |
+
return (
|
45 |
+
<div className="min-h-full flex flex-col">
|
46 |
+
<header className="z-10 shadow flex flex-row items-center md:justify-between h-14">
|
47 |
+
<Button
|
48 |
+
className={[
|
49 |
+
file ? '' : 'opacity-50 pointer-events-none',
|
50 |
+
'pl-1 pr-1 mx-1 sm:mx-5',
|
51 |
+
].join(' ')}
|
52 |
+
icon={<ArrowLeftIcon className="w-6 h-6" />}
|
53 |
+
onClick={() => {
|
54 |
+
setFile(undefined)
|
55 |
+
}}
|
56 |
+
>
|
57 |
+
<div className="md:w-[290px]">
|
58 |
+
<span className="hidden sm:inline select-none">
|
59 |
+
{m.start_new()}
|
60 |
+
</span>
|
61 |
+
</div>
|
62 |
+
</Button>
|
63 |
+
<div className="text-4xl font-bold text-blue-600 hover:text-blue-700 transition duration-300 ease-in-out">
|
64 |
+
Inpaint-web
|
65 |
+
</div>
|
66 |
+
<div className="hidden md:flex justify-end w-[300px] mx-1 sm:mx-5">
|
67 |
+
<Button
|
68 |
+
className="mr-5 flex"
|
69 |
+
onClick={() => {
|
70 |
+
if (languageTag() === 'zh') {
|
71 |
+
setLanguageTag('en')
|
72 |
+
} else {
|
73 |
+
setLanguageTag('zh')
|
74 |
+
}
|
75 |
+
}}
|
76 |
+
>
|
77 |
+
<p>{languageTag() === 'en' ? '切换到中文' : 'en'}</p>
|
78 |
+
</Button>
|
79 |
+
<Button
|
80 |
+
className="w-38 flex sm:visible"
|
81 |
+
icon={<InformationCircleIcon className="w-6 h-6" />}
|
82 |
+
onClick={() => {
|
83 |
+
setShowAbout(true)
|
84 |
+
}}
|
85 |
+
>
|
86 |
+
<p>{m.feedback()}</p>
|
87 |
+
</Button>
|
88 |
+
</div>
|
89 |
+
</header>
|
90 |
+
|
91 |
+
<main
|
92 |
+
style={{
|
93 |
+
height: 'calc(100vh - 56px)',
|
94 |
+
}}
|
95 |
+
className=" relative"
|
96 |
+
>
|
97 |
+
{file ? (
|
98 |
+
<Editor file={file} />
|
99 |
+
) : (
|
100 |
+
<>
|
101 |
+
<div className="flex h-full flex-1 flex-col items-center justify-center overflow-hidden">
|
102 |
+
<div className="h-72 sm:w-1/2 max-w-5xl">
|
103 |
+
<FileSelect
|
104 |
+
onSelection={async f => {
|
105 |
+
const { file: resizedFile } = await resizeImageFile(
|
106 |
+
f,
|
107 |
+
1024 * 4
|
108 |
+
)
|
109 |
+
setFile(resizedFile)
|
110 |
+
}}
|
111 |
+
/>
|
112 |
+
</div>
|
113 |
+
<div className="flex flex-col sm:flex-row pt-10 items-center justify-center cursor-pointer">
|
114 |
+
<span className="text-gray-500">{m.try_it_images()}</span>
|
115 |
+
<div className="flex space-x-2 sm:space-x-4 px-4">
|
116 |
+
{['bag', 'dog', 'car', 'bird', 'jacket', 'shoe', 'paris'].map(
|
117 |
+
image => (
|
118 |
+
<div
|
119 |
+
key={image}
|
120 |
+
onClick={() => startWithDemoImage(image)}
|
121 |
+
role="button"
|
122 |
+
onKeyDown={() => startWithDemoImage(image)}
|
123 |
+
tabIndex={-1}
|
124 |
+
>
|
125 |
+
<img
|
126 |
+
className="rounded-md hover:opacity-75 w-auto h-25"
|
127 |
+
src={`examples/${image}.jpeg`}
|
128 |
+
alt={image}
|
129 |
+
style={{ height: '100px' }}
|
130 |
+
/>
|
131 |
+
</div>
|
132 |
+
)
|
133 |
+
)}
|
134 |
+
</div>
|
135 |
+
</div>
|
136 |
+
</div>
|
137 |
+
</>
|
138 |
+
)}
|
139 |
+
</main>
|
140 |
+
|
141 |
+
{showAbout && (
|
142 |
+
<Modal>
|
143 |
+
<div ref={modalRef} className="text-xl space-y-5">
|
144 |
+
<p>
|
145 |
+
{' '}
|
146 |
+
任何问题到:{' '}
|
147 |
+
<a
|
148 |
+
href="https://github.com/lxfater/inpaint-web"
|
149 |
+
style={{ color: 'blue' }}
|
150 |
+
rel="noreferrer"
|
151 |
+
target="_blank"
|
152 |
+
>
|
153 |
+
Inpaint-web
|
154 |
+
</a>{' '}
|
155 |
+
反馈
|
156 |
+
</p>
|
157 |
+
<p>
|
158 |
+
{' '}
|
159 |
+
For any questions, please go to:{' '}
|
160 |
+
<a
|
161 |
+
href="https://github.com/lxfater/inpaint-web"
|
162 |
+
style={{ color: 'blue' }}
|
163 |
+
rel="noreferrer"
|
164 |
+
target="_blank"
|
165 |
+
>
|
166 |
+
Inpaint-web
|
167 |
+
</a>{' '}
|
168 |
+
to provide feedback.
|
169 |
+
</p>
|
170 |
+
</div>
|
171 |
+
</Modal>
|
172 |
+
)}
|
173 |
+
{!(downloadProgress === 100) && (
|
174 |
+
<Modal>
|
175 |
+
<div className="text-xl space-y-5">
|
176 |
+
<p>{m.inpaint_model_download_message()}</p>
|
177 |
+
<Progress percent={downloadProgress} />
|
178 |
+
</div>
|
179 |
+
</Modal>
|
180 |
+
)}
|
181 |
+
</div>
|
182 |
+
)
|
183 |
+
}
|
184 |
+
|
185 |
+
export default App
|
src/Editor.tsx
ADDED
@@ -0,0 +1,707 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
2 |
+
/* eslint-disable jsx-a11y/no-static-element-interactions */
|
3 |
+
import { DownloadIcon, EyeIcon, ViewBoardsIcon } from '@heroicons/react/outline'
|
4 |
+
import { useCallback, useEffect, useState, useRef, useMemo } from 'react'
|
5 |
+
import { useWindowSize } from 'react-use'
|
6 |
+
import inpaint from './adapters/inpainting'
|
7 |
+
import superResolution from './adapters/superResolution'
|
8 |
+
import Button from './components/Button'
|
9 |
+
import Slider from './components/Slider'
|
10 |
+
import { downloadImage, loadImage, useImage } from './utils'
|
11 |
+
import Progress from './components/Progress'
|
12 |
+
import { modelExists, downloadModel } from './adapters/cache'
|
13 |
+
import Modal from './components/Modal'
|
14 |
+
import * as m from './paraglide/messages'
|
15 |
+
|
16 |
+
interface EditorProps {
|
17 |
+
file: File
|
18 |
+
}
|
19 |
+
|
20 |
+
interface Line {
|
21 |
+
size?: number
|
22 |
+
pts: { x: number; y: number }[]
|
23 |
+
src: string
|
24 |
+
}
|
25 |
+
|
26 |
+
function drawLines(
|
27 |
+
ctx: CanvasRenderingContext2D,
|
28 |
+
lines: Line[],
|
29 |
+
color = 'rgba(255, 0, 0, 0.5)'
|
30 |
+
) {
|
31 |
+
ctx.strokeStyle = color
|
32 |
+
ctx.lineCap = 'round'
|
33 |
+
ctx.lineJoin = 'round'
|
34 |
+
|
35 |
+
lines.forEach(line => {
|
36 |
+
if (!line?.pts.length || !line.size) {
|
37 |
+
return
|
38 |
+
}
|
39 |
+
ctx.lineWidth = line.size
|
40 |
+
ctx.beginPath()
|
41 |
+
ctx.moveTo(line.pts[0].x, line.pts[0].y)
|
42 |
+
line.pts.forEach(pt => ctx.lineTo(pt.x, pt.y))
|
43 |
+
ctx.stroke()
|
44 |
+
})
|
45 |
+
}
|
46 |
+
|
47 |
+
const BRUSH_HIDE_ON_SLIDER_CHANGE_TIMEOUT = 2000
|
48 |
+
export default function Editor(props: EditorProps) {
|
49 |
+
const { file } = props
|
50 |
+
const [brushSize, setBrushSize] = useState(40)
|
51 |
+
const [original, isOriginalLoaded] = useImage(file)
|
52 |
+
const [renders, setRenders] = useState<HTMLImageElement[]>([])
|
53 |
+
const [context, setContext] = useState<CanvasRenderingContext2D>()
|
54 |
+
const [maskCanvas] = useState<HTMLCanvasElement>(() => {
|
55 |
+
return document.createElement('canvas')
|
56 |
+
})
|
57 |
+
const [lines, setLines] = useState<Line[]>([{ pts: [], src: '' }])
|
58 |
+
const brushRef = useRef<HTMLDivElement>(null)
|
59 |
+
const [showBrush, setShowBrush] = useState(false)
|
60 |
+
const [hideBrushTimeout, setHideBrushTimeout] = useState(0)
|
61 |
+
const [showOriginal, setShowOriginal] = useState(false)
|
62 |
+
const [isInpaintingLoading, setIsProcessingLoading] = useState(false)
|
63 |
+
const [generateProgress, setGenerateProgress] = useState(0)
|
64 |
+
const modalRef = useRef(null)
|
65 |
+
const [separator, setSeparator] = useState<HTMLDivElement>()
|
66 |
+
const [useSeparator, setUseSeparator] = useState(false)
|
67 |
+
const [originalImg, setOriginalImg] = useState<HTMLDivElement>()
|
68 |
+
const [separatorLeft, setSeparatorLeft] = useState(0)
|
69 |
+
const historyListRef = useRef<HTMLDivElement>(null)
|
70 |
+
const isBrushSizeChange = useRef<boolean>(false)
|
71 |
+
const scaledBrushSize = useMemo(() => brushSize, [brushSize])
|
72 |
+
const canvasDiv = useRef<HTMLDivElement>(null)
|
73 |
+
const [downloaded, setDownloaded] = useState(true)
|
74 |
+
const [downloadProgress, setDownloadProgress] = useState(0)
|
75 |
+
const windowSize = useWindowSize()
|
76 |
+
|
77 |
+
const draw = useCallback(
|
78 |
+
(index = -1) => {
|
79 |
+
if (!context) {
|
80 |
+
return
|
81 |
+
}
|
82 |
+
context.clearRect(0, 0, context.canvas.width, context.canvas.height)
|
83 |
+
const currRender =
|
84 |
+
renders[index === -1 ? renders.length - 1 : index] ?? original
|
85 |
+
const { canvas } = context
|
86 |
+
|
87 |
+
const divWidth = canvasDiv.current!.offsetWidth
|
88 |
+
const divHeight = canvasDiv.current!.offsetHeight
|
89 |
+
|
90 |
+
// 计算宽高比
|
91 |
+
const imgAspectRatio = currRender.width / currRender.height
|
92 |
+
const divAspectRatio = divWidth / divHeight
|
93 |
+
|
94 |
+
let canvasWidth
|
95 |
+
let canvasHeight
|
96 |
+
|
97 |
+
// 比较宽高比以决定如何缩放
|
98 |
+
if (divAspectRatio > imgAspectRatio) {
|
99 |
+
// div 较宽,基于高度缩放
|
100 |
+
canvasHeight = divHeight
|
101 |
+
canvasWidth = currRender.width * (divHeight / currRender.height)
|
102 |
+
} else {
|
103 |
+
// div 较窄,基于宽度缩放
|
104 |
+
canvasWidth = divWidth
|
105 |
+
canvasHeight = currRender.height * (divWidth / currRender.width)
|
106 |
+
}
|
107 |
+
|
108 |
+
canvas.width = canvasWidth
|
109 |
+
canvas.height = canvasHeight
|
110 |
+
|
111 |
+
if (currRender?.src) {
|
112 |
+
context.drawImage(currRender, 0, 0, canvas.width, canvas.height)
|
113 |
+
} else {
|
114 |
+
context.drawImage(original, 0, 0, canvas.width, canvas.height)
|
115 |
+
}
|
116 |
+
const currentLine = lines[lines.length - 1]
|
117 |
+
drawLines(context, [currentLine])
|
118 |
+
},
|
119 |
+
[context, lines, original, renders]
|
120 |
+
)
|
121 |
+
|
122 |
+
const refreshCanvasMask = useCallback(() => {
|
123 |
+
if (!context?.canvas.width || !context?.canvas.height) {
|
124 |
+
throw new Error('canvas has invalid size')
|
125 |
+
}
|
126 |
+
maskCanvas.width = context?.canvas.width
|
127 |
+
maskCanvas.height = context?.canvas.height
|
128 |
+
const ctx = maskCanvas.getContext('2d')
|
129 |
+
if (!ctx) {
|
130 |
+
throw new Error('could not retrieve mask canvas')
|
131 |
+
}
|
132 |
+
// Just need the finishing touch
|
133 |
+
const line = lines.slice(-1)[0]
|
134 |
+
if (line) drawLines(ctx, [line], 'white')
|
135 |
+
}, [context?.canvas.height, context?.canvas.width, lines, maskCanvas])
|
136 |
+
|
137 |
+
// Draw once the original image is loaded
|
138 |
+
useEffect(() => {
|
139 |
+
if (!context?.canvas) {
|
140 |
+
return
|
141 |
+
}
|
142 |
+
if (isOriginalLoaded) {
|
143 |
+
draw()
|
144 |
+
}
|
145 |
+
}, [context?.canvas, draw, original, isOriginalLoaded, windowSize])
|
146 |
+
|
147 |
+
// Handle mouse interactions
|
148 |
+
useEffect(() => {
|
149 |
+
const canvas = context?.canvas
|
150 |
+
if (!canvas) {
|
151 |
+
return
|
152 |
+
}
|
153 |
+
const onMouseMove = (ev: MouseEvent) => {
|
154 |
+
if (brushRef.current) {
|
155 |
+
const x = ev.pageX - scaledBrushSize / 2
|
156 |
+
const y = ev.pageY - scaledBrushSize / 2
|
157 |
+
|
158 |
+
brushRef.current.style.transform = `translate3d(${x}px, ${y}px, 0)`
|
159 |
+
}
|
160 |
+
}
|
161 |
+
const onPaint = (px: number, py: number) => {
|
162 |
+
const currLine = lines[lines.length - 1]
|
163 |
+
currLine.pts.push({ x: px, y: py })
|
164 |
+
draw()
|
165 |
+
}
|
166 |
+
const onMouseDrag = (ev: MouseEvent) => {
|
167 |
+
const px = ev.offsetX - canvas.offsetLeft
|
168 |
+
const py = ev.offsetY - canvas.offsetTop
|
169 |
+
onPaint(px, py)
|
170 |
+
}
|
171 |
+
|
172 |
+
const onPointerUp = async () => {
|
173 |
+
if (!original.src || showOriginal) {
|
174 |
+
return
|
175 |
+
}
|
176 |
+
if (lines.slice(-1)[0]?.pts.length === 0) {
|
177 |
+
return
|
178 |
+
}
|
179 |
+
const loading = onloading()
|
180 |
+
canvas.removeEventListener('mousemove', onMouseDrag)
|
181 |
+
canvas.removeEventListener('mouseup', onPointerUp)
|
182 |
+
refreshCanvasMask()
|
183 |
+
try {
|
184 |
+
const start = Date.now()
|
185 |
+
console.log('inpaint_start')
|
186 |
+
// each time based on the last result, the first is the original
|
187 |
+
const newFile = renders.slice(-1)[0] ?? file
|
188 |
+
const res = await inpaint(newFile, maskCanvas.toDataURL())
|
189 |
+
if (!res) {
|
190 |
+
throw new Error('empty response')
|
191 |
+
}
|
192 |
+
// TODO: fix the render if it failed loading
|
193 |
+
const newRender = new Image()
|
194 |
+
newRender.dataset.id = Date.now().toString()
|
195 |
+
await loadImage(newRender, res)
|
196 |
+
renders.push(newRender)
|
197 |
+
lines.push({ pts: [], src: '' } as Line)
|
198 |
+
setRenders([...renders])
|
199 |
+
setLines([...lines])
|
200 |
+
console.log('inpaint_processed', {
|
201 |
+
duration: Date.now() - start,
|
202 |
+
})
|
203 |
+
} catch (e: any) {
|
204 |
+
console.log('inpaint_failed', {
|
205 |
+
error: e,
|
206 |
+
})
|
207 |
+
// eslint-disable-next-line
|
208 |
+
alert(e.message ? e.message : e.toString())
|
209 |
+
}
|
210 |
+
if (historyListRef.current) {
|
211 |
+
const { scrollWidth, clientWidth } = historyListRef.current
|
212 |
+
if (scrollWidth > clientWidth) {
|
213 |
+
historyListRef.current.scrollTo(scrollWidth, 0)
|
214 |
+
}
|
215 |
+
}
|
216 |
+
loading.close()
|
217 |
+
draw()
|
218 |
+
}
|
219 |
+
canvas.addEventListener('mousemove', onMouseMove)
|
220 |
+
|
221 |
+
const onTouchMove = (ev: TouchEvent) => {
|
222 |
+
ev.preventDefault()
|
223 |
+
ev.stopPropagation()
|
224 |
+
const currLine = lines[lines.length - 1]
|
225 |
+
const coords = canvas.getBoundingClientRect()
|
226 |
+
currLine.pts.push({
|
227 |
+
x: ev.touches[0].clientX - coords.x,
|
228 |
+
y: ev.touches[0].clientY - coords.y,
|
229 |
+
})
|
230 |
+
draw()
|
231 |
+
}
|
232 |
+
const onPointerStart = () => {
|
233 |
+
if (!original.src || showOriginal) {
|
234 |
+
return
|
235 |
+
}
|
236 |
+
const currLine = lines[lines.length - 1]
|
237 |
+
currLine.size = brushSize
|
238 |
+
canvas.addEventListener('mousemove', onMouseDrag)
|
239 |
+
canvas.addEventListener('mouseup', onPointerUp)
|
240 |
+
// onPaint(e)
|
241 |
+
}
|
242 |
+
|
243 |
+
canvas.addEventListener('touchstart', onPointerStart)
|
244 |
+
canvas.addEventListener('touchmove', onTouchMove)
|
245 |
+
canvas.addEventListener('touchend', onPointerUp)
|
246 |
+
canvas.onmouseenter = () => {
|
247 |
+
window.clearTimeout(hideBrushTimeout)
|
248 |
+
setShowBrush(true && !showOriginal)
|
249 |
+
}
|
250 |
+
canvas.onmouseleave = () => setShowBrush(false)
|
251 |
+
canvas.onmousedown = onPointerStart
|
252 |
+
|
253 |
+
return () => {
|
254 |
+
canvas.removeEventListener('mousemove', onMouseDrag)
|
255 |
+
canvas.removeEventListener('mousemove', onMouseMove)
|
256 |
+
canvas.removeEventListener('mouseup', onPointerUp)
|
257 |
+
canvas.removeEventListener('touchstart', onPointerStart)
|
258 |
+
canvas.removeEventListener('touchmove', onTouchMove)
|
259 |
+
canvas.removeEventListener('touchend', onPointerUp)
|
260 |
+
canvas.onmouseenter = null
|
261 |
+
canvas.onmouseleave = null
|
262 |
+
canvas.onmousedown = null
|
263 |
+
}
|
264 |
+
}, [
|
265 |
+
brushSize,
|
266 |
+
context,
|
267 |
+
file,
|
268 |
+
draw,
|
269 |
+
lines,
|
270 |
+
refreshCanvasMask,
|
271 |
+
maskCanvas,
|
272 |
+
original.src,
|
273 |
+
renders,
|
274 |
+
showOriginal,
|
275 |
+
hideBrushTimeout,
|
276 |
+
])
|
277 |
+
|
278 |
+
useEffect(() => {
|
279 |
+
if (!separator || !originalImg) return
|
280 |
+
|
281 |
+
const separatorMove = (ev: MouseEvent) => {
|
282 |
+
ev.preventDefault()
|
283 |
+
ev.stopPropagation()
|
284 |
+
if (context?.canvas) {
|
285 |
+
const { width } = context?.canvas
|
286 |
+
const canvasRect = context?.canvas.getBoundingClientRect()
|
287 |
+
const separatorOffsetLeft = ev.pageX - canvasRect.left
|
288 |
+
if (separatorOffsetLeft <= width && separatorOffsetLeft >= 0) {
|
289 |
+
setSeparatorLeft(separatorOffsetLeft)
|
290 |
+
} else if (separatorOffsetLeft < 0) {
|
291 |
+
setSeparatorLeft(0)
|
292 |
+
} else if (separatorOffsetLeft > width) {
|
293 |
+
setSeparatorLeft(width)
|
294 |
+
}
|
295 |
+
}
|
296 |
+
}
|
297 |
+
|
298 |
+
const separatorDown = () => {
|
299 |
+
window.addEventListener('mousemove', separatorMove)
|
300 |
+
setUseSeparator(true)
|
301 |
+
}
|
302 |
+
|
303 |
+
const separatorUp = () => {
|
304 |
+
window.removeEventListener('mousemove', separatorMove)
|
305 |
+
setUseSeparator(false)
|
306 |
+
}
|
307 |
+
|
308 |
+
separator.addEventListener('mousedown', separatorDown)
|
309 |
+
window.addEventListener('mouseup', separatorUp)
|
310 |
+
|
311 |
+
return () => {
|
312 |
+
separator.removeEventListener('mousedown', separatorDown)
|
313 |
+
window.removeEventListener('mouseup', separatorUp)
|
314 |
+
}
|
315 |
+
}, [separator, context])
|
316 |
+
|
317 |
+
function download() {
|
318 |
+
const currRender = renders.at(-1) ?? original
|
319 |
+
downloadImage(currRender.currentSrc, 'IMG')
|
320 |
+
}
|
321 |
+
|
322 |
+
const undo = useCallback(async () => {
|
323 |
+
const l = lines
|
324 |
+
l.pop()
|
325 |
+
l.pop()
|
326 |
+
setLines([...l, { pts: [], src: '' }])
|
327 |
+
const r = renders
|
328 |
+
r.pop()
|
329 |
+
setRenders([...r])
|
330 |
+
}, [lines, renders])
|
331 |
+
|
332 |
+
useEffect(() => {
|
333 |
+
const handler = (event: KeyboardEvent) => {
|
334 |
+
if (!renders.length) {
|
335 |
+
return
|
336 |
+
}
|
337 |
+
const isCmdZ = (event.metaKey || event.ctrlKey) && event.key === 'z'
|
338 |
+
if (isCmdZ) {
|
339 |
+
event.preventDefault()
|
340 |
+
undo()
|
341 |
+
}
|
342 |
+
}
|
343 |
+
window.addEventListener('keydown', handler)
|
344 |
+
return () => {
|
345 |
+
window.removeEventListener('keydown', handler)
|
346 |
+
}
|
347 |
+
}, [renders, undo])
|
348 |
+
|
349 |
+
const backTo = useCallback(
|
350 |
+
(index: number) => {
|
351 |
+
lines.splice(index + 1)
|
352 |
+
setLines([...lines, { pts: [], src: '' }])
|
353 |
+
renders.splice(index + 1)
|
354 |
+
setRenders([...renders])
|
355 |
+
},
|
356 |
+
[renders, lines]
|
357 |
+
)
|
358 |
+
|
359 |
+
const History = useMemo(
|
360 |
+
() =>
|
361 |
+
renders.map((render, index) => {
|
362 |
+
return (
|
363 |
+
<div
|
364 |
+
key={render.dataset.id}
|
365 |
+
style={{
|
366 |
+
position: 'relative',
|
367 |
+
display: 'inline-block',
|
368 |
+
flexShrink: 0,
|
369 |
+
}}
|
370 |
+
>
|
371 |
+
<img
|
372 |
+
src={render.src}
|
373 |
+
alt="render"
|
374 |
+
className="rounded-sm"
|
375 |
+
style={{
|
376 |
+
height: '90px',
|
377 |
+
}}
|
378 |
+
/>
|
379 |
+
<Button
|
380 |
+
className="hover:opacity-100 opacity-0 cursor-pointer rounded-sm"
|
381 |
+
style={{
|
382 |
+
position: 'absolute',
|
383 |
+
top: '0',
|
384 |
+
left: '0',
|
385 |
+
width: '100%',
|
386 |
+
height: '100%',
|
387 |
+
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
388 |
+
display: 'flex',
|
389 |
+
alignItems: 'center',
|
390 |
+
justifyContent: 'center',
|
391 |
+
}}
|
392 |
+
onClick={() => backTo(index)}
|
393 |
+
onEnter={() => draw(index)}
|
394 |
+
onLeave={draw}
|
395 |
+
>
|
396 |
+
<div
|
397 |
+
style={{
|
398 |
+
color: '#fff',
|
399 |
+
fontSize: '12px',
|
400 |
+
textAlign: 'center',
|
401 |
+
}}
|
402 |
+
>
|
403 |
+
回到这
|
404 |
+
<br />
|
405 |
+
Back here
|
406 |
+
</div>
|
407 |
+
</Button>
|
408 |
+
</div>
|
409 |
+
)
|
410 |
+
}),
|
411 |
+
[renders, backTo]
|
412 |
+
)
|
413 |
+
|
414 |
+
const handleSliderStart = () => {
|
415 |
+
setShowBrush(true)
|
416 |
+
}
|
417 |
+
const handleSliderChange = (sliderValue: number) => {
|
418 |
+
if (!isBrushSizeChange.current) {
|
419 |
+
isBrushSizeChange.current = true
|
420 |
+
}
|
421 |
+
if (brushRef.current) {
|
422 |
+
const x = document.documentElement.clientWidth / 2 - scaledBrushSize / 2
|
423 |
+
const y = document.documentElement.clientHeight / 2 - scaledBrushSize / 2
|
424 |
+
|
425 |
+
brushRef.current.style.transform = `translate3d(${x}px, ${y}px, 0)`
|
426 |
+
}
|
427 |
+
setBrushSize(sliderValue)
|
428 |
+
window.clearTimeout(hideBrushTimeout)
|
429 |
+
setHideBrushTimeout(
|
430 |
+
window.setTimeout(() => {
|
431 |
+
setShowBrush(false)
|
432 |
+
}, BRUSH_HIDE_ON_SLIDER_CHANGE_TIMEOUT)
|
433 |
+
)
|
434 |
+
}
|
435 |
+
|
436 |
+
const onloading = useCallback(() => {
|
437 |
+
setIsProcessingLoading(true)
|
438 |
+
setGenerateProgress(0)
|
439 |
+
const progressTimer = window.setInterval(() => {
|
440 |
+
setGenerateProgress(p => {
|
441 |
+
if (p < 90) return p + 10 * Math.random()
|
442 |
+
if (p >= 90 && p < 99) return p + 1 * Math.random()
|
443 |
+
// Do not hide the progress bar after 99%,cause sometimes long time progress
|
444 |
+
// window.setTimeout(() => setIsInpaintingLoading(false), 500)
|
445 |
+
return p
|
446 |
+
})
|
447 |
+
}, 1000)
|
448 |
+
return {
|
449 |
+
close: () => {
|
450 |
+
clearInterval(progressTimer)
|
451 |
+
setGenerateProgress(100)
|
452 |
+
setIsProcessingLoading(false)
|
453 |
+
},
|
454 |
+
}
|
455 |
+
}, [])
|
456 |
+
|
457 |
+
const onSuperResolution = useCallback(async () => {
|
458 |
+
if (!(await modelExists('superResolution'))) {
|
459 |
+
setDownloaded(false)
|
460 |
+
await downloadModel('superResolution', setDownloadProgress)
|
461 |
+
setDownloaded(true)
|
462 |
+
}
|
463 |
+
setIsProcessingLoading(true)
|
464 |
+
try {
|
465 |
+
// 运行
|
466 |
+
const start = Date.now()
|
467 |
+
console.log('superResolution_start')
|
468 |
+
// each time based on the last result, the first is the original
|
469 |
+
const newFile = renders.at(-1) ?? file
|
470 |
+
const res = await superResolution(newFile, setGenerateProgress)
|
471 |
+
if (!res) {
|
472 |
+
throw new Error('empty response')
|
473 |
+
}
|
474 |
+
// TODO: fix the render if it failed loading
|
475 |
+
const newRender = new Image()
|
476 |
+
newRender.dataset.id = Date.now().toString()
|
477 |
+
await loadImage(newRender, res)
|
478 |
+
renders.push(newRender)
|
479 |
+
lines.push({ pts: [], src: '' } as Line)
|
480 |
+
setRenders([...renders])
|
481 |
+
setLines([...lines])
|
482 |
+
console.log('superResolution_processed', {
|
483 |
+
duration: Date.now() - start,
|
484 |
+
})
|
485 |
+
|
486 |
+
// 替换当前图片
|
487 |
+
} catch (error) {
|
488 |
+
console.error('superResolution', error)
|
489 |
+
} finally {
|
490 |
+
setIsProcessingLoading(false)
|
491 |
+
}
|
492 |
+
}, [file, lines, original.naturalHeight, original.naturalWidth, renders])
|
493 |
+
|
494 |
+
return (
|
495 |
+
<div
|
496 |
+
className={[
|
497 |
+
'flex flex-col items-center h-full justify-between',
|
498 |
+
isInpaintingLoading ? 'animate-pulse-fast pointer-events-none' : '',
|
499 |
+
].join(' ')}
|
500 |
+
>
|
501 |
+
{/* History */}
|
502 |
+
<div
|
503 |
+
ref={historyListRef}
|
504 |
+
style={{
|
505 |
+
height: '116px',
|
506 |
+
}}
|
507 |
+
className={[
|
508 |
+
'flex-shrink-0',
|
509 |
+
'mt-4 border p-3 rounded',
|
510 |
+
'flex items-left w-full max-w-4xl',
|
511 |
+
'space-y-0 flex-row space-x-5',
|
512 |
+
'scrollbar-thin scrollbar-thumb-black scrollbar-track-primary overflow-x-scroll',
|
513 |
+
].join(' ')}
|
514 |
+
>
|
515 |
+
{History}
|
516 |
+
</div>
|
517 |
+
{/* 画图 */}
|
518 |
+
<div
|
519 |
+
className={[
|
520 |
+
'flex-grow',
|
521 |
+
'flex justify-center',
|
522 |
+
'my-2',
|
523 |
+
'relative',
|
524 |
+
].join(' ')}
|
525 |
+
style={{
|
526 |
+
width: '70vw',
|
527 |
+
}}
|
528 |
+
ref={canvasDiv}
|
529 |
+
>
|
530 |
+
<div className="relative">
|
531 |
+
<canvas
|
532 |
+
className="rounded-sm"
|
533 |
+
style={showBrush ? { cursor: 'none' } : {}}
|
534 |
+
ref={r => {
|
535 |
+
if (r && !context) {
|
536 |
+
const ctx = r.getContext('2d')
|
537 |
+
if (ctx) {
|
538 |
+
setContext(ctx)
|
539 |
+
}
|
540 |
+
}
|
541 |
+
}}
|
542 |
+
/>
|
543 |
+
<div
|
544 |
+
className={[
|
545 |
+
'absolute top-0 right-0 pointer-events-none',
|
546 |
+
showOriginal ? '' : 'overflow-hidden',
|
547 |
+
].join(' ')}
|
548 |
+
style={{
|
549 |
+
width: showOriginal ? `${context?.canvas.width}px` : '0px',
|
550 |
+
height: context?.canvas.height,
|
551 |
+
transitionProperty: 'width, height',
|
552 |
+
transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
553 |
+
transitionDuration: '300ms',
|
554 |
+
}}
|
555 |
+
ref={r => {
|
556 |
+
if (r && !originalImg) {
|
557 |
+
setOriginalImg(r)
|
558 |
+
}
|
559 |
+
}}
|
560 |
+
>
|
561 |
+
<div
|
562 |
+
className={[
|
563 |
+
'absolute top-0 right-0 pointer-events-none z-10',
|
564 |
+
useSeparator ? 'bg-black text-white' : 'bg-primary ',
|
565 |
+
'w-1',
|
566 |
+
'flex items-center justify-center',
|
567 |
+
'separator',
|
568 |
+
].join(' ')}
|
569 |
+
style={{
|
570 |
+
left: `${separatorLeft}px`,
|
571 |
+
height: context?.canvas.height,
|
572 |
+
transitionProperty: 'width, height',
|
573 |
+
transitionTimingFunction: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
574 |
+
transitionDuration: '300ms',
|
575 |
+
}}
|
576 |
+
>
|
577 |
+
<span className="absolute left-1 bottom-0 p-1 bg-opacity-25 bg-black rounded text-white select-none">
|
578 |
+
original
|
579 |
+
</span>
|
580 |
+
<div
|
581 |
+
className={[
|
582 |
+
'absolute py-2 px-1 rounded-md pointer-events-auto',
|
583 |
+
useSeparator ? 'bg-black' : 'bg-primary ',
|
584 |
+
].join(' ')}
|
585 |
+
style={{ cursor: 'ew-resize' }}
|
586 |
+
ref={r => {
|
587 |
+
if (r && !separator) {
|
588 |
+
setSeparator(r)
|
589 |
+
}
|
590 |
+
}}
|
591 |
+
>
|
592 |
+
<ViewBoardsIcon
|
593 |
+
className="w-5 h-5"
|
594 |
+
style={{ cursor: 'ew-resize' }}
|
595 |
+
/>
|
596 |
+
</div>
|
597 |
+
</div>
|
598 |
+
<img
|
599 |
+
className="absolute right-0"
|
600 |
+
src={original.src}
|
601 |
+
alt="original"
|
602 |
+
width={`${context?.canvas.width}px`}
|
603 |
+
height={`${context?.canvas.height}px`}
|
604 |
+
style={{
|
605 |
+
width: `${context?.canvas.width}px`,
|
606 |
+
height: `${context?.canvas.height}px`,
|
607 |
+
maxWidth: 'none',
|
608 |
+
clipPath: `inset(0 0 0 ${separatorLeft}px)`,
|
609 |
+
}}
|
610 |
+
/>
|
611 |
+
</div>
|
612 |
+
{isInpaintingLoading && (
|
613 |
+
<div className="z-10 bg-white absolute bg-opacity-80 top-0 left-0 right-0 bottom-0 h-full w-full flex justify-center items-center">
|
614 |
+
<div ref={modalRef} className="text-xl space-y-5 w-4/5 sm:w-1/2">
|
615 |
+
<p>正在处理中,请耐心等待。。。</p>
|
616 |
+
<p>It is being processed, please be patient...</p>
|
617 |
+
<Progress percent={generateProgress} />
|
618 |
+
</div>
|
619 |
+
</div>
|
620 |
+
)}
|
621 |
+
</div>
|
622 |
+
</div>
|
623 |
+
|
624 |
+
{!downloaded && (
|
625 |
+
<Modal>
|
626 |
+
<div className="text-xl space-y-5">
|
627 |
+
<p>{m.upscaleing_model_download_message()}</p>
|
628 |
+
<Progress percent={downloadProgress} />
|
629 |
+
</div>
|
630 |
+
</Modal>
|
631 |
+
)}
|
632 |
+
{showBrush && (
|
633 |
+
<div
|
634 |
+
className="fixed rounded-full bg-red-500 bg-opacity-50 pointer-events-none left-0 top-0"
|
635 |
+
style={{
|
636 |
+
width: `${scaledBrushSize}px`,
|
637 |
+
height: `${scaledBrushSize}px`,
|
638 |
+
transform: `translate3d(-100px, -100px, 0)`,
|
639 |
+
}}
|
640 |
+
ref={brushRef}
|
641 |
+
/>
|
642 |
+
)}
|
643 |
+
{/* 工具栏 */}
|
644 |
+
<div
|
645 |
+
className={[
|
646 |
+
'flex-shrink-0',
|
647 |
+
'bg-white rounded-md border border-gray-300 hover:border-gray-400 shadow-md hover:shadow-lg p-4 transition duration-200 ease-in-out',
|
648 |
+
'flex items-center w-full max-w-4xl py-6 mb-4, justify-between',
|
649 |
+
'flex-col space-y-2 sm:space-y-0 sm:flex-row sm:space-x-5',
|
650 |
+
].join(' ')}
|
651 |
+
>
|
652 |
+
{renders.length > 0 && (
|
653 |
+
<Button
|
654 |
+
primary
|
655 |
+
onClick={undo}
|
656 |
+
icon={
|
657 |
+
<svg
|
658 |
+
className="w-6 h-6"
|
659 |
+
width="19"
|
660 |
+
height="9"
|
661 |
+
viewBox="0 0 19 9"
|
662 |
+
fill="none"
|
663 |
+
xmlns="http://www.w3.org/2000/svg"
|
664 |
+
>
|
665 |
+
<path
|
666 |
+
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"
|
667 |
+
fill="currentColor"
|
668 |
+
/>
|
669 |
+
</svg>
|
670 |
+
}
|
671 |
+
>
|
672 |
+
{m.undo()}
|
673 |
+
</Button>
|
674 |
+
)}
|
675 |
+
<Slider
|
676 |
+
label={m.bruch_size()}
|
677 |
+
min={10}
|
678 |
+
max={200}
|
679 |
+
value={brushSize}
|
680 |
+
onChange={handleSliderChange}
|
681 |
+
onStart={handleSliderStart}
|
682 |
+
/>
|
683 |
+
<Button
|
684 |
+
primary={showOriginal}
|
685 |
+
icon={<EyeIcon className="w-6 h-6" />}
|
686 |
+
onUp={() => {
|
687 |
+
setShowOriginal(!showOriginal)
|
688 |
+
setTimeout(() => setSeparatorLeft(0), 300)
|
689 |
+
}}
|
690 |
+
>
|
691 |
+
{m.original()}
|
692 |
+
</Button>
|
693 |
+
{!showOriginal && (
|
694 |
+
<Button onUp={onSuperResolution}>{m.upscale()}</Button>
|
695 |
+
)}
|
696 |
+
|
697 |
+
<Button
|
698 |
+
primary
|
699 |
+
icon={<DownloadIcon className="w-6 h-6" />}
|
700 |
+
onClick={download}
|
701 |
+
>
|
702 |
+
{m.download()}
|
703 |
+
</Button>
|
704 |
+
</div>
|
705 |
+
</div>
|
706 |
+
)
|
707 |
+
}
|
src/adapters/cache.ts
ADDED
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import localforage from 'localforage'
|
2 |
+
|
3 |
+
export type modelType = 'inpaint' | 'superResolution'
|
4 |
+
|
5 |
+
localforage.config({
|
6 |
+
name: 'modelCache',
|
7 |
+
})
|
8 |
+
|
9 |
+
export async function saveModel(modelType: modelType, modelBlob: ArrayBuffer) {
|
10 |
+
await localforage.setItem(getModel(modelType).name, modelBlob)
|
11 |
+
}
|
12 |
+
|
13 |
+
function getModel(modelType: modelType) {
|
14 |
+
if (modelType === 'inpaint') {
|
15 |
+
const modelList = [
|
16 |
+
{
|
17 |
+
name: 'model',
|
18 |
+
url: 'https://huggingface.co/lxfater/inpaint-web/resolve/main/migan.onnx',
|
19 |
+
backupUrl: '',
|
20 |
+
},
|
21 |
+
{
|
22 |
+
name: 'model-perf',
|
23 |
+
url: 'https://huggingface.co/andraniksargsyan/migan/resolve/main/migan.onnx',
|
24 |
+
backupUrl: '',
|
25 |
+
},
|
26 |
+
{
|
27 |
+
name: 'migan-pipeline-v2',
|
28 |
+
url: 'https://huggingface.co/andraniksargsyan/migan/resolve/main/migan_pipeline_v2.onnx',
|
29 |
+
backupUrl:
|
30 |
+
'https://worker-share-proxy-01f5.lxfater.workers.dev/andraniksargsyan/migan/resolve/main/migan_pipeline_v2.onnx',
|
31 |
+
},
|
32 |
+
]
|
33 |
+
const currentModel = modelList[2]
|
34 |
+
return currentModel
|
35 |
+
}
|
36 |
+
if (modelType === 'superResolution') {
|
37 |
+
const modelList = [
|
38 |
+
{
|
39 |
+
name: 'realesrgan-x4',
|
40 |
+
url: 'https://huggingface.co/lxfater/inpaint-web/resolve/main/realesrgan-x4.onnx',
|
41 |
+
backupUrl:
|
42 |
+
'https://worker-share-proxy-01f5.lxfater.workers.dev/lxfater/inpaint-web/resolve/main/realesrgan-x4.onnx',
|
43 |
+
},
|
44 |
+
]
|
45 |
+
const currentModel = modelList[0]
|
46 |
+
return currentModel
|
47 |
+
}
|
48 |
+
throw new Error('wrong modelType')
|
49 |
+
}
|
50 |
+
|
51 |
+
export async function loadModel(modelType: modelType): Promise<ArrayBuffer> {
|
52 |
+
const model = (await localforage.getItem(
|
53 |
+
getModel(modelType).name
|
54 |
+
)) as ArrayBuffer
|
55 |
+
return model
|
56 |
+
}
|
57 |
+
|
58 |
+
export async function modelExists(modelType: modelType) {
|
59 |
+
const model = await loadModel(modelType)
|
60 |
+
return model !== null && model !== undefined
|
61 |
+
}
|
62 |
+
|
63 |
+
export async function ensureModel(modelType: modelType) {
|
64 |
+
if (await modelExists(modelType)) {
|
65 |
+
return loadModel(modelType)
|
66 |
+
}
|
67 |
+
const model = getModel(modelType)
|
68 |
+
const response = await fetch(model.url)
|
69 |
+
const buffer = await response.arrayBuffer()
|
70 |
+
await saveModel(modelType, buffer)
|
71 |
+
return buffer
|
72 |
+
}
|
73 |
+
|
74 |
+
export async function downloadModel(
|
75 |
+
modelType: modelType,
|
76 |
+
setDownloadProgress: (arg0: number) => void
|
77 |
+
) {
|
78 |
+
if (await modelExists(modelType)) {
|
79 |
+
return
|
80 |
+
}
|
81 |
+
|
82 |
+
async function downloadFromUrl(url: string) {
|
83 |
+
console.log('start download from', url)
|
84 |
+
setDownloadProgress(0)
|
85 |
+
const response = await fetch(url)
|
86 |
+
const fullSize = response.headers.get('content-length')
|
87 |
+
const reader = response.body!.getReader()
|
88 |
+
const total: Uint8Array[] = []
|
89 |
+
let downloaded = 0
|
90 |
+
|
91 |
+
while (true) {
|
92 |
+
const { done, value } = await reader.read()
|
93 |
+
|
94 |
+
if (done) {
|
95 |
+
break
|
96 |
+
}
|
97 |
+
|
98 |
+
downloaded += value?.length || 0
|
99 |
+
|
100 |
+
if (value) {
|
101 |
+
total.push(value)
|
102 |
+
}
|
103 |
+
|
104 |
+
setDownloadProgress((downloaded / Number(fullSize)) * 100)
|
105 |
+
}
|
106 |
+
|
107 |
+
const buffer = new Uint8Array(downloaded)
|
108 |
+
let offset = 0
|
109 |
+
for (const chunk of total) {
|
110 |
+
buffer.set(chunk, offset)
|
111 |
+
offset += chunk.length
|
112 |
+
}
|
113 |
+
|
114 |
+
await saveModel(modelType, buffer)
|
115 |
+
setDownloadProgress(100)
|
116 |
+
}
|
117 |
+
|
118 |
+
const model = getModel(modelType)
|
119 |
+
try {
|
120 |
+
await downloadFromUrl(model.url)
|
121 |
+
} catch (e) {
|
122 |
+
if (model.backupUrl) {
|
123 |
+
try {
|
124 |
+
await downloadFromUrl(model.backupUrl)
|
125 |
+
} catch (r) {
|
126 |
+
alert(`Failed to download the backup model: ${r}`)
|
127 |
+
}
|
128 |
+
}
|
129 |
+
alert(`Failed to download the model, network problem: ${e}`)
|
130 |
+
}
|
131 |
+
}
|
src/adapters/inpainting.ts
ADDED
@@ -0,0 +1,271 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// @ts-nocheck
|
2 |
+
/* eslint-disable camelcase */
|
3 |
+
/* eslint-disable no-plusplus */
|
4 |
+
import cv, { Mat } from 'opencv-ts'
|
5 |
+
import { ensureModel } from './cache'
|
6 |
+
import { getCapabilities } from './util'
|
7 |
+
import type { modelType } from './cache'
|
8 |
+
// ort.env.debug = true
|
9 |
+
// ort.env.logLevel = 'verbose'
|
10 |
+
// ort.env.webgpu.profilingMode = 'default'
|
11 |
+
|
12 |
+
function loadImage(url: string): Promise<HTMLImageElement> {
|
13 |
+
return new Promise((resolve, reject) => {
|
14 |
+
const img = new Image()
|
15 |
+
img.crossOrigin = 'Anonymous'
|
16 |
+
img.onload = () => resolve(img)
|
17 |
+
img.onerror = () => reject(new Error(`Failed to load image from ${url}`))
|
18 |
+
img.src = url
|
19 |
+
})
|
20 |
+
}
|
21 |
+
function imgProcess(img: Mat) {
|
22 |
+
const channels = new cv.MatVector()
|
23 |
+
cv.split(img, channels) // 分割通道
|
24 |
+
|
25 |
+
const C = channels.size() // 通道数
|
26 |
+
const H = img.rows // 图像高度
|
27 |
+
const W = img.cols // 图像宽度
|
28 |
+
|
29 |
+
const chwArray = new Uint8Array(C * H * W) // 创建新的数组来存储转换后的数据
|
30 |
+
|
31 |
+
for (let c = 0; c < C; c++) {
|
32 |
+
const channelData = channels.get(c).data // 获取单个通道的数据
|
33 |
+
for (let h = 0; h < H; h++) {
|
34 |
+
for (let w = 0; w < W; w++) {
|
35 |
+
chwArray[c * H * W + h * W + w] = channelData[h * W + w]
|
36 |
+
// chwArray[c * H * W + h * W + w] = channelData[h * W + w]
|
37 |
+
}
|
38 |
+
}
|
39 |
+
}
|
40 |
+
|
41 |
+
channels.delete() // 清理内存
|
42 |
+
return chwArray // 返回转换后的数据
|
43 |
+
}
|
44 |
+
function markProcess(img: Mat) {
|
45 |
+
const channels = new cv.MatVector()
|
46 |
+
cv.split(img, channels) // 分割通道
|
47 |
+
|
48 |
+
const C = 1 // 通道数
|
49 |
+
const H = img.rows // 图像高度
|
50 |
+
const W = img.cols // 图像宽度
|
51 |
+
|
52 |
+
const chwArray = new Uint8Array(C * H * W) // 创建新的数组来存储转换后的数据
|
53 |
+
|
54 |
+
for (let c = 0; c < C; c++) {
|
55 |
+
const channelData = channels.get(0).data // 获取单个通道的数据
|
56 |
+
for (let h = 0; h < H; h++) {
|
57 |
+
for (let w = 0; w < W; w++) {
|
58 |
+
chwArray[c * H * W + h * W + w] = (channelData[h * W + w] !== 255) * 255
|
59 |
+
}
|
60 |
+
}
|
61 |
+
}
|
62 |
+
|
63 |
+
channels.delete() // 清理内存
|
64 |
+
return chwArray // 返回转换后的数据
|
65 |
+
}
|
66 |
+
function processImage(
|
67 |
+
img: HTMLImageElement,
|
68 |
+
canvasId?: string
|
69 |
+
): Promise<Uint8Array> {
|
70 |
+
return new Promise((resolve, reject) => {
|
71 |
+
try {
|
72 |
+
const src = cv.imread(img)
|
73 |
+
const src_rgb = new cv.Mat()
|
74 |
+
// 将图像从RGBA转换为RGB
|
75 |
+
cv.cvtColor(src, src_rgb, cv.COLOR_RGBA2RGB)
|
76 |
+
if (canvasId) {
|
77 |
+
cv.imshow(canvasId, src_rgb)
|
78 |
+
}
|
79 |
+
resolve(imgProcess(src_rgb))
|
80 |
+
|
81 |
+
src.delete()
|
82 |
+
src_rgb.delete()
|
83 |
+
} catch (error) {
|
84 |
+
reject(error)
|
85 |
+
}
|
86 |
+
})
|
87 |
+
}
|
88 |
+
|
89 |
+
function processMark(
|
90 |
+
img: HTMLImageElement,
|
91 |
+
canvasId?: string
|
92 |
+
): Promise<Uint8Array> {
|
93 |
+
return new Promise((resolve, reject) => {
|
94 |
+
try {
|
95 |
+
const src = cv.imread(img)
|
96 |
+
const src_grey = new cv.Mat()
|
97 |
+
|
98 |
+
// 将图像从RGBA转换为二值化
|
99 |
+
cv.cvtColor(src, src_grey, cv.COLOR_BGR2GRAY)
|
100 |
+
|
101 |
+
if (canvasId) {
|
102 |
+
cv.imshow(canvasId, src_grey)
|
103 |
+
}
|
104 |
+
|
105 |
+
resolve(markProcess(src_grey))
|
106 |
+
|
107 |
+
src.delete()
|
108 |
+
} catch (error) {
|
109 |
+
reject(error)
|
110 |
+
}
|
111 |
+
})
|
112 |
+
}
|
113 |
+
function postProcess(uint8Data: Uint8Array, width: number, height: number) {
|
114 |
+
const chwToHwcData = []
|
115 |
+
const size = width * height
|
116 |
+
|
117 |
+
for (let h = 0; h < height; h++) {
|
118 |
+
for (let w = 0; w < width; w++) {
|
119 |
+
for (let c = 0; c < 3; c++) {
|
120 |
+
// RGB通道
|
121 |
+
const chwIndex = c * size + h * width + w
|
122 |
+
const pixelVal = uint8Data[chwIndex]
|
123 |
+
let newPiex = pixelVal
|
124 |
+
if (pixelVal > 255) {
|
125 |
+
newPiex = 255
|
126 |
+
} else if (pixelVal < 0) {
|
127 |
+
newPiex = 0
|
128 |
+
}
|
129 |
+
chwToHwcData.push(newPiex) // 归一化反转
|
130 |
+
}
|
131 |
+
chwToHwcData.push(255) // Alpha通道
|
132 |
+
}
|
133 |
+
}
|
134 |
+
return chwToHwcData
|
135 |
+
}
|
136 |
+
|
137 |
+
function imageDataToDataURL(imageData) {
|
138 |
+
// 创建 canvas
|
139 |
+
const canvas = document.createElement('canvas')
|
140 |
+
canvas.width = imageData.width
|
141 |
+
canvas.height = imageData.height
|
142 |
+
|
143 |
+
// 绘制 imageData 到 canvas
|
144 |
+
const ctx = canvas.getContext('2d')
|
145 |
+
ctx.putImageData(imageData, 0, 0)
|
146 |
+
|
147 |
+
// 导出为数据 URL
|
148 |
+
return canvas.toDataURL()
|
149 |
+
}
|
150 |
+
|
151 |
+
function configEnv(capabilities) {
|
152 |
+
ort.env.wasm.wasmPaths =
|
153 |
+
'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.16.3/dist/'
|
154 |
+
if (capabilities.webgpu) {
|
155 |
+
ort.env.wasm.numThreads = 1
|
156 |
+
} else {
|
157 |
+
if (capabilities.threads) {
|
158 |
+
ort.env.wasm.numThreads = navigator.hardwareConcurrency ?? 4
|
159 |
+
}
|
160 |
+
if (capabilities.simd) {
|
161 |
+
ort.env.wasm.simd = true
|
162 |
+
}
|
163 |
+
ort.env.wasm.proxy = true
|
164 |
+
}
|
165 |
+
console.log('env', ort.env.wasm)
|
166 |
+
}
|
167 |
+
const resizeMark = (
|
168 |
+
image: HTMLImageElement,
|
169 |
+
width: number,
|
170 |
+
height: number
|
171 |
+
): Promise<HTMLImageElement> => {
|
172 |
+
return new Promise((resolve, reject) => {
|
173 |
+
const canvas = document.createElement('canvas')
|
174 |
+
canvas.width = width
|
175 |
+
canvas.height = height
|
176 |
+
|
177 |
+
// 将图片绘制到canvas上,并调整大小
|
178 |
+
const ctx = canvas.getContext('2d')
|
179 |
+
if (!ctx) {
|
180 |
+
reject(new Error('Unable to get canvas context'))
|
181 |
+
return
|
182 |
+
}
|
183 |
+
ctx.drawImage(image, 0, 0, width, height)
|
184 |
+
|
185 |
+
// 获取调整大小后的图片URL
|
186 |
+
const resizedImageUrl = canvas.toDataURL()
|
187 |
+
|
188 |
+
// 创建一个新的Image对象并设置其src为调整大小后的图片URL
|
189 |
+
const resizedImage = new Image()
|
190 |
+
resizedImage.onload = () => resolve(resizedImage)
|
191 |
+
resizedImage.onerror = () =>
|
192 |
+
reject(new Error('Failed to load resized image'))
|
193 |
+
resizedImage.src = resizedImageUrl
|
194 |
+
})
|
195 |
+
}
|
196 |
+
let model: ArrayBuffer | null = null
|
197 |
+
export default async function inpaint(
|
198 |
+
imageFile: File | HTMLImageElement,
|
199 |
+
maskBase64: string
|
200 |
+
) {
|
201 |
+
console.time('sessionCreate')
|
202 |
+
if (!model) {
|
203 |
+
const capabilities = await getCapabilities()
|
204 |
+
configEnv(capabilities)
|
205 |
+
const modelBuffer = await ensureModel('inpaint')
|
206 |
+
model = await ort.InferenceSession.create(modelBuffer, {
|
207 |
+
executionProviders: [capabilities.webgpu ? 'webgpu' : 'wasm'],
|
208 |
+
})
|
209 |
+
}
|
210 |
+
console.timeEnd('sessionCreate')
|
211 |
+
console.time('preProcess')
|
212 |
+
|
213 |
+
const [originalImg, originalMark] = await Promise.all([
|
214 |
+
imageFile instanceof HTMLImageElement
|
215 |
+
? imageFile
|
216 |
+
: loadImage(URL.createObjectURL(imageFile)),
|
217 |
+
loadImage(maskBase64),
|
218 |
+
])
|
219 |
+
|
220 |
+
const [img, mark] = await Promise.all([
|
221 |
+
processImage(originalImg),
|
222 |
+
processMark(
|
223 |
+
await resizeMark(originalMark, originalImg.width, originalImg.height)
|
224 |
+
),
|
225 |
+
])
|
226 |
+
|
227 |
+
const imageTensor = new ort.Tensor('uint8', img, [
|
228 |
+
1,
|
229 |
+
3,
|
230 |
+
originalImg.height,
|
231 |
+
originalImg.width,
|
232 |
+
])
|
233 |
+
|
234 |
+
const maskTensor = new ort.Tensor('uint8', mark, [
|
235 |
+
1,
|
236 |
+
1,
|
237 |
+
originalImg.height,
|
238 |
+
originalImg.width,
|
239 |
+
])
|
240 |
+
|
241 |
+
const Feed: {
|
242 |
+
[key: string]: any
|
243 |
+
} = {
|
244 |
+
[model.inputNames[0]]: imageTensor,
|
245 |
+
[model.inputNames[1]]: maskTensor,
|
246 |
+
}
|
247 |
+
|
248 |
+
console.timeEnd('preProcess')
|
249 |
+
|
250 |
+
console.time('run')
|
251 |
+
const results = await model.run(Feed)
|
252 |
+
console.timeEnd('run')
|
253 |
+
|
254 |
+
console.time('postProcess')
|
255 |
+
const outsTensor = results[model.outputNames[0]]
|
256 |
+
const chwToHwcData = postProcess(
|
257 |
+
outsTensor.data,
|
258 |
+
originalImg.width,
|
259 |
+
originalImg.height
|
260 |
+
)
|
261 |
+
const imageData = new ImageData(
|
262 |
+
new Uint8ClampedArray(chwToHwcData),
|
263 |
+
originalImg.width,
|
264 |
+
originalImg.height
|
265 |
+
)
|
266 |
+
console.log(imageData, 'imageData')
|
267 |
+
const result = imageDataToDataURL(imageData)
|
268 |
+
console.timeEnd('postProcess')
|
269 |
+
|
270 |
+
return result
|
271 |
+
}
|
src/adapters/superResolution.ts
ADDED
@@ -0,0 +1,297 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/* eslint-disable no-console */
|
2 |
+
/* eslint-disable no-plusplus */
|
3 |
+
import cv, { Mat } from 'opencv-ts'
|
4 |
+
import { getCapabilities } from './util'
|
5 |
+
import { ensureModel } from './cache'
|
6 |
+
|
7 |
+
function loadImage(url: string): Promise<HTMLImageElement> {
|
8 |
+
return new Promise((resolve, reject) => {
|
9 |
+
const img = new Image()
|
10 |
+
img.crossOrigin = 'Anonymous'
|
11 |
+
img.onload = () => resolve(img)
|
12 |
+
img.onerror = () => reject(new Error(`Failed to load image from ${url}`))
|
13 |
+
img.src = url
|
14 |
+
})
|
15 |
+
}
|
16 |
+
function imgProcess(img: Mat) {
|
17 |
+
const channels = new cv.MatVector()
|
18 |
+
cv.split(img, channels) // 分割通道
|
19 |
+
|
20 |
+
const C = channels.size() // 通道数
|
21 |
+
const H = img.rows // 图像高度
|
22 |
+
const W = img.cols // 图像宽度
|
23 |
+
|
24 |
+
const chwArray = new Float32Array(C * H * W) // 创建新的数组来存储转换后的数据
|
25 |
+
|
26 |
+
for (let c = 0; c < C; c++) {
|
27 |
+
const channelData = channels.get(c).data // 获取单个通道的数据
|
28 |
+
for (let h = 0; h < H; h++) {
|
29 |
+
for (let w = 0; w < W; w++) {
|
30 |
+
chwArray[c * H * W + h * W + w] = channelData[h * W + w] / 255.0
|
31 |
+
// chwArray[c * H * W + h * W + w] = channelData[h * W + w]
|
32 |
+
}
|
33 |
+
}
|
34 |
+
}
|
35 |
+
|
36 |
+
channels.delete() // 清理内存
|
37 |
+
return chwArray // 返回转换后的数据
|
38 |
+
}
|
39 |
+
async function tileProc(
|
40 |
+
inputTensor: ort.Tensor,
|
41 |
+
session: ort.InferenceSession,
|
42 |
+
callback: (progress: number) => void
|
43 |
+
) {
|
44 |
+
const inputDims = inputTensor.dims
|
45 |
+
const imageW = inputDims[3]
|
46 |
+
const imageH = inputDims[2]
|
47 |
+
|
48 |
+
const rOffset = 0
|
49 |
+
const gOffset = imageW * imageH
|
50 |
+
const bOffset = imageW * imageH * 2
|
51 |
+
|
52 |
+
const outputDims = [
|
53 |
+
inputDims[0],
|
54 |
+
inputDims[1],
|
55 |
+
inputDims[2] * 4,
|
56 |
+
inputDims[3] * 4,
|
57 |
+
]
|
58 |
+
const outputTensor = new ort.Tensor(
|
59 |
+
'float32',
|
60 |
+
new Float32Array(
|
61 |
+
outputDims[0] * outputDims[1] * outputDims[2] * outputDims[3]
|
62 |
+
),
|
63 |
+
outputDims
|
64 |
+
)
|
65 |
+
|
66 |
+
const outImageW = outputDims[3]
|
67 |
+
const outImageH = outputDims[2]
|
68 |
+
const outROffset = 0
|
69 |
+
const outGOffset = outImageW * outImageH
|
70 |
+
const outBOffset = outImageW * outImageH * 2
|
71 |
+
|
72 |
+
const tileSize = 64
|
73 |
+
const tilePadding = 6
|
74 |
+
const tileSizePre = tileSize - tilePadding * 2
|
75 |
+
|
76 |
+
const tilesx = Math.ceil(inputDims[3] / tileSizePre)
|
77 |
+
const tilesy = Math.ceil(inputDims[2] / tileSizePre)
|
78 |
+
|
79 |
+
const { data } = inputTensor
|
80 |
+
|
81 |
+
console.log(inputTensor)
|
82 |
+
const numTiles = tilesx * tilesy
|
83 |
+
let currentTile = 0
|
84 |
+
|
85 |
+
for (let i = 0; i < tilesx; i++) {
|
86 |
+
for (let j = 0; j < tilesy; j++) {
|
87 |
+
const ti = Date.now()
|
88 |
+
const tileW = Math.min(tileSizePre, imageW - i * tileSizePre)
|
89 |
+
const tileH = Math.min(tileSizePre, imageH - j * tileSizePre)
|
90 |
+
console.log(`tileW: ${tileW} tileH: ${tileH}`)
|
91 |
+
const tileROffset = 0
|
92 |
+
const tileGOffset = tileSize * tileSize
|
93 |
+
const tileBOffset = tileSize * tileSize * 2
|
94 |
+
|
95 |
+
// padding tile 转移到上面的数据上
|
96 |
+
const tileData = new Float32Array(tileSize * tileSize * 3)
|
97 |
+
for (let xp = -tilePadding; xp < tileSizePre + tilePadding; xp++) {
|
98 |
+
for (let yp = -tilePadding; yp < tileSizePre + tilePadding; yp++) {
|
99 |
+
// 计算在data中的一维坐标,防止边缘溢出
|
100 |
+
let xim = i * tileSizePre + xp
|
101 |
+
if (xim < 0) xim = 0
|
102 |
+
else if (xim >= imageW) xim = imageW - 1
|
103 |
+
|
104 |
+
// 计算在data中的一维坐标,防止边缘溢出
|
105 |
+
let yim = j * tileSizePre + yp
|
106 |
+
if (yim < 0) yim = 0
|
107 |
+
else if (yim >= imageH) yim = imageH - 1
|
108 |
+
|
109 |
+
const idx = xim + yim * imageW
|
110 |
+
|
111 |
+
const xt = xp + tilePadding
|
112 |
+
const yt = yp + tilePadding
|
113 |
+
// const idx = (i * tileSize + x) + (j * tileSize + y) * imageW;
|
114 |
+
// 主要转化到一维的坐标上,
|
115 |
+
tileData[xt + yt * tileSize + tileROffset] = data[idx + rOffset]
|
116 |
+
tileData[xt + yt * tileSize + tileGOffset] = data[idx + gOffset]
|
117 |
+
tileData[xt + yt * tileSize + tileBOffset] = data[idx + bOffset]
|
118 |
+
}
|
119 |
+
}
|
120 |
+
|
121 |
+
const tile = new ort.Tensor('float32', tileData, [
|
122 |
+
1,
|
123 |
+
3,
|
124 |
+
tileSize,
|
125 |
+
tileSize,
|
126 |
+
])
|
127 |
+
const r = await session.run({ 'input.1': tile })
|
128 |
+
const results = {
|
129 |
+
output: r['1895'],
|
130 |
+
}
|
131 |
+
console.log(`pre dims:${results.output.dims}`)
|
132 |
+
|
133 |
+
const outTileW = tileW * 4
|
134 |
+
const outTileH = tileH * 4
|
135 |
+
const outTileSize = tileSize * 4
|
136 |
+
const outTileSizePre = tileSizePre * 4
|
137 |
+
|
138 |
+
const outTileROffset = 0
|
139 |
+
const outTileGOffset = outTileSize * outTileSize
|
140 |
+
const outTileBOffset = outTileSize * outTileSize * 2
|
141 |
+
|
142 |
+
// add tile to output,直接输出
|
143 |
+
for (let x = 0; x < outTileW; x++) {
|
144 |
+
for (let y = 0; y < outTileH; y++) {
|
145 |
+
const xim = i * outTileSizePre + x
|
146 |
+
const yim = j * outTileSizePre + y
|
147 |
+
const idx = xim + yim * outImageW
|
148 |
+
const xt = x + tilePadding * 4
|
149 |
+
const yt = y + tilePadding * 4
|
150 |
+
outputTensor.data[idx + outROffset] =
|
151 |
+
results.output.data[xt + yt * outTileSize + outTileROffset]
|
152 |
+
outputTensor.data[idx + outGOffset] =
|
153 |
+
results.output.data[xt + yt * outTileSize + outTileGOffset]
|
154 |
+
outputTensor.data[idx + outBOffset] =
|
155 |
+
results.output.data[xt + yt * outTileSize + outTileBOffset]
|
156 |
+
}
|
157 |
+
}
|
158 |
+
currentTile++
|
159 |
+
const dt = Date.now() - ti
|
160 |
+
const remTime = (numTiles - currentTile) * dt
|
161 |
+
console.log(
|
162 |
+
`tile ${currentTile} of ${numTiles} took ${dt} ms, remaining time: ${remTime} ms`
|
163 |
+
)
|
164 |
+
callback(Math.round(100 * (currentTile / numTiles)))
|
165 |
+
}
|
166 |
+
}
|
167 |
+
console.log(`output dims:${outputTensor.dims}`)
|
168 |
+
return outputTensor
|
169 |
+
}
|
170 |
+
function processImage(
|
171 |
+
img: HTMLImageElement,
|
172 |
+
canvasId?: string
|
173 |
+
): Promise<Uint8Array> {
|
174 |
+
return new Promise((resolve, reject) => {
|
175 |
+
try {
|
176 |
+
const src = cv.imread(img)
|
177 |
+
// eslint-disable-next-line camelcase
|
178 |
+
const src_rgb = new cv.Mat()
|
179 |
+
// 将图像从RGBA转换为RGB
|
180 |
+
cv.cvtColor(src, src_rgb, cv.COLOR_RGBA2RGB)
|
181 |
+
if (canvasId) {
|
182 |
+
cv.imshow(canvasId, src_rgb)
|
183 |
+
}
|
184 |
+
resolve(imgProcess(src_rgb))
|
185 |
+
|
186 |
+
src.delete()
|
187 |
+
src_rgb.delete()
|
188 |
+
} catch (error) {
|
189 |
+
reject(error)
|
190 |
+
}
|
191 |
+
})
|
192 |
+
}
|
193 |
+
function configEnv(capabilities: {
|
194 |
+
webgpu: any
|
195 |
+
wasm?: boolean
|
196 |
+
simd: any
|
197 |
+
threads: any
|
198 |
+
}) {
|
199 |
+
ort.env.wasm.wasmPaths =
|
200 |
+
'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.16.3/dist/'
|
201 |
+
if (capabilities.webgpu) {
|
202 |
+
ort.env.wasm.numThreads = 1
|
203 |
+
} else {
|
204 |
+
if (capabilities.threads) {
|
205 |
+
ort.env.wasm.numThreads = navigator.hardwareConcurrency ?? 4
|
206 |
+
}
|
207 |
+
if (capabilities.simd) {
|
208 |
+
ort.env.wasm.simd = true
|
209 |
+
}
|
210 |
+
ort.env.wasm.proxy = true
|
211 |
+
}
|
212 |
+
console.log('env', ort.env.wasm)
|
213 |
+
}
|
214 |
+
function postProcess(floatData: Float32Array, width: number, height: number) {
|
215 |
+
const chwToHwcData = []
|
216 |
+
const size = width * height
|
217 |
+
|
218 |
+
for (let h = 0; h < height; h++) {
|
219 |
+
for (let w = 0; w < width; w++) {
|
220 |
+
for (let c = 0; c < 3; c++) {
|
221 |
+
// RGB通道
|
222 |
+
const chwIndex = c * size + h * width + w
|
223 |
+
const pixelVal = floatData[chwIndex]
|
224 |
+
let newPiex = pixelVal
|
225 |
+
if (pixelVal > 1) {
|
226 |
+
newPiex = 1
|
227 |
+
} else if (pixelVal < 0) {
|
228 |
+
newPiex = 0
|
229 |
+
}
|
230 |
+
chwToHwcData.push(newPiex * 255) // 归一化反转
|
231 |
+
}
|
232 |
+
chwToHwcData.push(255) // Alpha通道
|
233 |
+
}
|
234 |
+
}
|
235 |
+
return chwToHwcData
|
236 |
+
}
|
237 |
+
|
238 |
+
function imageDataToDataURL(imageData: ImageData) {
|
239 |
+
// 创建 canvas
|
240 |
+
const canvas = document.createElement('canvas')
|
241 |
+
canvas.width = imageData.width
|
242 |
+
canvas.height = imageData.height
|
243 |
+
|
244 |
+
// 绘制 imageData 到 canvas
|
245 |
+
const ctx = canvas.getContext('2d')
|
246 |
+
ctx.putImageData(imageData, 0, 0)
|
247 |
+
|
248 |
+
// 导出为数据 URL
|
249 |
+
return canvas.toDataURL()
|
250 |
+
}
|
251 |
+
let model: ArrayBuffer | null = null
|
252 |
+
export default async function superResolution(
|
253 |
+
imageFile: File | HTMLImageElement,
|
254 |
+
callback: (progress: number) => void
|
255 |
+
) {
|
256 |
+
console.time('sessionCreate')
|
257 |
+
if (!model) {
|
258 |
+
const capabilities = await getCapabilities()
|
259 |
+
configEnv(capabilities)
|
260 |
+
const modelBuffer = await ensureModel('superResolution')
|
261 |
+
model = await ort.InferenceSession.create(modelBuffer, {
|
262 |
+
executionProviders: [capabilities.webgpu ? 'webgpu' : 'wasm'],
|
263 |
+
})
|
264 |
+
}
|
265 |
+
console.timeEnd('sessionCreate')
|
266 |
+
|
267 |
+
const img =
|
268 |
+
imageFile instanceof HTMLImageElement
|
269 |
+
? imageFile
|
270 |
+
: await loadImage(URL.createObjectURL(imageFile))
|
271 |
+
const imageTersorData = await processImage(img)
|
272 |
+
const imageTensor = new ort.Tensor('float32', imageTersorData, [
|
273 |
+
1,
|
274 |
+
3,
|
275 |
+
img.height,
|
276 |
+
img.width,
|
277 |
+
])
|
278 |
+
|
279 |
+
const result = await tileProc(imageTensor, model, callback)
|
280 |
+
console.time('postProcess')
|
281 |
+
const outsTensor = result
|
282 |
+
const chwToHwcData = postProcess(
|
283 |
+
outsTensor.data,
|
284 |
+
img.width * 4,
|
285 |
+
img.height * 4
|
286 |
+
)
|
287 |
+
const imageData = new ImageData(
|
288 |
+
new Uint8ClampedArray(chwToHwcData),
|
289 |
+
img.width * 4,
|
290 |
+
img.height * 4
|
291 |
+
)
|
292 |
+
console.log(imageData, 'imageData')
|
293 |
+
const url = imageDataToDataURL(imageData)
|
294 |
+
console.timeEnd('postProcess')
|
295 |
+
|
296 |
+
return url
|
297 |
+
}
|
src/adapters/util.ts
ADDED
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
export async function checkWebgpu() {
|
2 |
+
// @ts-ignore
|
3 |
+
if (!navigator.gpu) {
|
4 |
+
return false
|
5 |
+
}
|
6 |
+
// @ts-ignore
|
7 |
+
const adapter = await navigator.gpu.requestAdapter()
|
8 |
+
if (!adapter) {
|
9 |
+
return false
|
10 |
+
}
|
11 |
+
return true
|
12 |
+
}
|
13 |
+
export const wasm = () =>
|
14 |
+
typeof WebAssembly === 'object' &&
|
15 |
+
typeof WebAssembly.instantiate === 'function'
|
16 |
+
export const threads = () =>
|
17 |
+
(async e => {
|
18 |
+
try {
|
19 |
+
return (
|
20 |
+
typeof MessageChannel !== 'undefined' &&
|
21 |
+
new MessageChannel().port1.postMessage(new SharedArrayBuffer(1)),
|
22 |
+
WebAssembly.validate(e)
|
23 |
+
)
|
24 |
+
// eslint-disable-next-line @typescript-eslint/no-shadow
|
25 |
+
} catch (e) {
|
26 |
+
return !1
|
27 |
+
}
|
28 |
+
})(
|
29 |
+
new Uint8Array([
|
30 |
+
0, 97, 115, 109, 1, 0, 0, 0, 1, 4, 1, 96, 0, 0, 3, 2, 1, 0, 5, 4, 1, 3, 1,
|
31 |
+
1, 10, 11, 1, 9, 0, 65, 0, 254, 16, 2, 0, 26, 11,
|
32 |
+
])
|
33 |
+
)
|
34 |
+
export const simd = async () =>
|
35 |
+
WebAssembly.validate(
|
36 |
+
new Uint8Array([
|
37 |
+
0, 97, 115, 109, 1, 0, 0, 0, 1, 5, 1, 96, 0, 1, 123, 3, 2, 1, 0, 10, 10,
|
38 |
+
1, 8, 0, 65, 0, 253, 15, 253, 98, 11,
|
39 |
+
])
|
40 |
+
)
|
41 |
+
|
42 |
+
export const getCapabilities = async () => {
|
43 |
+
return {
|
44 |
+
webgpu: await checkWebgpu(),
|
45 |
+
wasm: wasm(),
|
46 |
+
simd: await simd(),
|
47 |
+
threads: await threads(),
|
48 |
+
}
|
49 |
+
}
|
50 |
+
const version = '1.16.3'
|
51 |
+
export const getTagSrc = async () => {
|
52 |
+
const prefix = `https://cdn.jsdelivr.net/npm/onnxruntime-web@${version}/dist/`
|
53 |
+
const capablilities = await getCapabilities()
|
54 |
+
if (capablilities.webgpu) {
|
55 |
+
return `${prefix}ort.webgpu.min.js`
|
56 |
+
}
|
57 |
+
if (capablilities.wasm) {
|
58 |
+
if (capablilities.simd || capablilities.threads) {
|
59 |
+
return `${prefix}ort.wasm.min.js`
|
60 |
+
}
|
61 |
+
return `${prefix}ort.wasm-core.min.js`
|
62 |
+
}
|
63 |
+
return `${prefix}ort.min.js`
|
64 |
+
}
|
65 |
+
|
66 |
+
export const loadingOnnxruntime = async () => {
|
67 |
+
const script = document.createElement('script')
|
68 |
+
|
69 |
+
// 设置script标签的属性,例如src
|
70 |
+
script.src = await getTagSrc() // 替换为您要加载的脚本的URL
|
71 |
+
|
72 |
+
// 将script标签添加到文档的head部分
|
73 |
+
document.head.appendChild(script)
|
74 |
+
}
|
75 |
+
|
76 |
+
export async function checkGpu() {
|
77 |
+
return !navigator?.gpu && !(await navigator.gpu?.requestAdapter())
|
78 |
+
}
|
src/components/Button.tsx
ADDED
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { ReactNode, useState } from 'react'
|
2 |
+
|
3 |
+
interface ButtonProps {
|
4 |
+
children: ReactNode
|
5 |
+
className?: string
|
6 |
+
icon?: ReactNode
|
7 |
+
primary?: boolean
|
8 |
+
style?: {
|
9 |
+
[key: string]: string
|
10 |
+
}
|
11 |
+
onClick?: () => void
|
12 |
+
onDown?: () => void
|
13 |
+
onUp?: () => void
|
14 |
+
onEnter?: () => void
|
15 |
+
onLeave?: () => void
|
16 |
+
}
|
17 |
+
|
18 |
+
export default function Button(props: ButtonProps) {
|
19 |
+
const {
|
20 |
+
children,
|
21 |
+
className,
|
22 |
+
icon,
|
23 |
+
primary,
|
24 |
+
style,
|
25 |
+
onClick,
|
26 |
+
onDown,
|
27 |
+
onUp,
|
28 |
+
onEnter,
|
29 |
+
onLeave,
|
30 |
+
} = props
|
31 |
+
const [active, setActive] = useState(false)
|
32 |
+
let background = ''
|
33 |
+
if (primary) {
|
34 |
+
background = 'bg-primary hover:bg-black hover:text-white'
|
35 |
+
}
|
36 |
+
if (active) {
|
37 |
+
background = 'bg-black text-white'
|
38 |
+
}
|
39 |
+
if (!primary && !active) {
|
40 |
+
background = 'hover:bg-primary'
|
41 |
+
}
|
42 |
+
return (
|
43 |
+
<div
|
44 |
+
role="button"
|
45 |
+
onKeyDown={() => {
|
46 |
+
onDown?.()
|
47 |
+
}}
|
48 |
+
onClick={onClick}
|
49 |
+
onPointerDown={() => {
|
50 |
+
setActive(true)
|
51 |
+
onDown?.()
|
52 |
+
}}
|
53 |
+
onPointerUp={() => {
|
54 |
+
setActive(false)
|
55 |
+
onUp?.()
|
56 |
+
}}
|
57 |
+
onPointerEnter={() => {
|
58 |
+
onEnter?.()
|
59 |
+
}}
|
60 |
+
onPointerLeave={() => {
|
61 |
+
onLeave?.()
|
62 |
+
}}
|
63 |
+
tabIndex={-1}
|
64 |
+
className={[
|
65 |
+
'inline-flex space-x-3 py-3 px-5 rounded-md cursor-pointer',
|
66 |
+
background,
|
67 |
+
className,
|
68 |
+
].join(' ')}
|
69 |
+
style={style}
|
70 |
+
>
|
71 |
+
{icon}
|
72 |
+
<span className="whitespace-nowrap select-none">{children}</span>
|
73 |
+
</div>
|
74 |
+
)
|
75 |
+
}
|
src/components/FileSelect.tsx
ADDED
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useState } from 'react'
|
2 |
+
import * as m from '../paraglide/messages'
|
3 |
+
|
4 |
+
type FileSelectProps = {
|
5 |
+
onSelection: (file: File) => void
|
6 |
+
}
|
7 |
+
|
8 |
+
export default function FileSelect(props: FileSelectProps) {
|
9 |
+
const { onSelection } = props
|
10 |
+
|
11 |
+
const [dragHover, setDragHover] = useState(false)
|
12 |
+
const [uploadElemId] = useState(`file-upload-${Math.random().toString()}`)
|
13 |
+
|
14 |
+
function onFileSelected(file: File) {
|
15 |
+
if (!file) {
|
16 |
+
return
|
17 |
+
}
|
18 |
+
// Skip non-image files
|
19 |
+
const isImage = file.type.match('image.*')
|
20 |
+
if (!isImage) {
|
21 |
+
return
|
22 |
+
}
|
23 |
+
try {
|
24 |
+
// Check if file is larger than 10mb
|
25 |
+
if (file.size > 10 * 1024 * 1024) {
|
26 |
+
throw new Error('file too large')
|
27 |
+
}
|
28 |
+
onSelection(file)
|
29 |
+
} catch (e) {
|
30 |
+
// eslint-disable-next-line
|
31 |
+
alert(`error: ${(e as any).message}`)
|
32 |
+
}
|
33 |
+
}
|
34 |
+
|
35 |
+
async function getFile(entry: any): Promise<File> {
|
36 |
+
return new Promise(resolve => {
|
37 |
+
entry.file((file: File) => resolve(file))
|
38 |
+
})
|
39 |
+
}
|
40 |
+
|
41 |
+
/* eslint-disable no-await-in-loop */
|
42 |
+
|
43 |
+
// Drop handler function to get all files
|
44 |
+
async function getAllFileEntries(items: DataTransferItemList) {
|
45 |
+
const fileEntries: Array<File> = []
|
46 |
+
// Use BFS to traverse entire directory/file structure
|
47 |
+
const queue = []
|
48 |
+
// Unfortunately items is not iterable i.e. no forEach
|
49 |
+
for (let i = 0; i < items.length; i += 1) {
|
50 |
+
queue.push(items[i].webkitGetAsEntry())
|
51 |
+
}
|
52 |
+
while (queue.length > 0) {
|
53 |
+
const entry = queue.shift()
|
54 |
+
if (entry?.isFile) {
|
55 |
+
// Only append images
|
56 |
+
const file = await getFile(entry)
|
57 |
+
fileEntries.push(file)
|
58 |
+
} else if (entry?.isDirectory) {
|
59 |
+
queue.push(
|
60 |
+
...(await readAllDirectoryEntries((entry as any).createReader()))
|
61 |
+
)
|
62 |
+
}
|
63 |
+
}
|
64 |
+
return fileEntries
|
65 |
+
}
|
66 |
+
|
67 |
+
// Get all the entries (files or sub-directories) in a directory
|
68 |
+
// by calling readEntries until it returns empty array
|
69 |
+
async function readAllDirectoryEntries(directoryReader: any) {
|
70 |
+
const entries = []
|
71 |
+
let readEntries = await readEntriesPromise(directoryReader)
|
72 |
+
while (readEntries.length > 0) {
|
73 |
+
entries.push(...readEntries)
|
74 |
+
readEntries = await readEntriesPromise(directoryReader)
|
75 |
+
}
|
76 |
+
return entries
|
77 |
+
}
|
78 |
+
|
79 |
+
/* eslint-enable no-await-in-loop */
|
80 |
+
|
81 |
+
// Wrap readEntries in a promise to make working with readEntries easier
|
82 |
+
// readEntries will return only some of the entries in a directory
|
83 |
+
// e.g. Chrome returns at most 100 entries at a time
|
84 |
+
async function readEntriesPromise(directoryReader: any): Promise<any> {
|
85 |
+
return new Promise((resolve, reject) => {
|
86 |
+
directoryReader.readEntries(resolve, reject)
|
87 |
+
})
|
88 |
+
}
|
89 |
+
|
90 |
+
async function handleDrop(ev: React.DragEvent) {
|
91 |
+
ev.preventDefault()
|
92 |
+
const items = await getAllFileEntries(ev.dataTransfer.items)
|
93 |
+
setDragHover(false)
|
94 |
+
onFileSelected(items[0])
|
95 |
+
}
|
96 |
+
|
97 |
+
return (
|
98 |
+
<label
|
99 |
+
htmlFor={uploadElemId}
|
100 |
+
className="block w-full h-full group relative cursor-pointer rounded-md font-medium focus-within:outline-none"
|
101 |
+
>
|
102 |
+
<div
|
103 |
+
className={[
|
104 |
+
'w-full h-full flex items-center justify-center px-6 pt-5 pb-6 text-xl',
|
105 |
+
'border-4 border-dashed rounded-md',
|
106 |
+
'hover:border-black hover:bg-primary',
|
107 |
+
'text-center',
|
108 |
+
dragHover ? 'border-black bg-primary' : 'bg-gray-100 border-gray-300',
|
109 |
+
].join(' ')}
|
110 |
+
onDrop={handleDrop}
|
111 |
+
onDragOver={ev => {
|
112 |
+
ev.stopPropagation()
|
113 |
+
ev.preventDefault()
|
114 |
+
setDragHover(true)
|
115 |
+
}}
|
116 |
+
onDragLeave={() => setDragHover(false)}
|
117 |
+
>
|
118 |
+
<input
|
119 |
+
id={uploadElemId}
|
120 |
+
name={uploadElemId}
|
121 |
+
type="file"
|
122 |
+
className="sr-only"
|
123 |
+
onChange={ev => {
|
124 |
+
const file = ev.currentTarget.files?.[0]
|
125 |
+
if (file) {
|
126 |
+
onFileSelected(file)
|
127 |
+
}
|
128 |
+
}}
|
129 |
+
accept="image/png, image/jpeg, image/webp"
|
130 |
+
/>
|
131 |
+
<p>{m.drop_zone()}</p>
|
132 |
+
</div>
|
133 |
+
</label>
|
134 |
+
)
|
135 |
+
}
|
src/components/Link.tsx
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
interface LinkProps {
|
2 |
+
children: string
|
3 |
+
href: string
|
4 |
+
}
|
5 |
+
|
6 |
+
export default function Link(props: LinkProps) {
|
7 |
+
const { children, href } = props
|
8 |
+
return (
|
9 |
+
<a href={href} className="font-black underline">
|
10 |
+
{children}
|
11 |
+
</a>
|
12 |
+
)
|
13 |
+
}
|
src/components/Logo.tsx
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
export default function Logo() {
|
2 |
+
return (
|
3 |
+
<svg
|
4 |
+
width="421"
|
5 |
+
height="78"
|
6 |
+
viewBox="0 0 421 78"
|
7 |
+
fill="none"
|
8 |
+
xmlns="http://www.w3.org/2000/svg"
|
9 |
+
>
|
10 |
+
<path d="M102 61H414" stroke="#BDFF01" strokeWidth="9" />
|
11 |
+
<path
|
12 |
+
d="M127.767 43.728C127.335 45.576 126.591 47.064 125.535 48.192C124.503 49.32 123.243 50.136 121.755 50.64C120.267 51.12 118.611 51.36 116.787 51.36C114.291 51.36 112.131 50.88 110.307 49.92C108.507 48.96 107.115 47.568 106.131 45.744C105.171 43.92 104.691 41.712 104.691 39.12C104.691 36.528 105.171 34.32 106.131 32.496C107.115 30.672 108.507 29.28 110.307 28.32C112.131 27.36 114.291 26.88 116.787 26.88C118.539 26.88 120.171 27.132 121.683 27.636C123.195 28.116 124.467 28.884 125.499 29.94C126.555 30.996 127.251 32.376 127.587 34.08L120.387 36.888C120.195 35.328 119.799 34.32 119.199 33.864C118.623 33.408 117.891 33.18 117.003 33.18C115.755 33.18 114.819 33.66 114.195 34.62C113.571 35.556 113.259 37.056 113.259 39.12C113.259 41.16 113.547 42.66 114.123 43.62C114.723 44.58 115.743 45.06 117.183 45.06C118.047 45.06 118.755 44.808 119.307 44.304C119.859 43.776 120.207 42.948 120.351 41.82L127.767 43.728ZM137.694 25.8V43.836C137.694 44.556 137.802 45.084 138.018 45.42C138.234 45.756 138.618 45.924 139.17 45.924C139.434 45.924 139.662 45.912 139.854 45.888C140.07 45.84 140.274 45.792 140.466 45.744L140.106 50.208C139.65 50.544 138.99 50.82 138.126 51.036C137.286 51.252 136.458 51.36 135.642 51.36C133.41 51.36 131.814 50.88 130.854 49.92C129.894 48.96 129.414 47.352 129.414 45.096V25.8H137.694ZM151.295 51.36C148.031 51.36 145.499 50.544 143.699 48.912C141.899 47.28 140.999 44.976 140.999 42C140.999 40.008 141.407 38.316 142.223 36.924C143.063 35.532 144.227 34.476 145.715 33.756C147.227 33.012 148.979 32.64 150.971 32.64C153.011 32.64 154.739 33.012 156.155 33.756C157.595 34.476 158.687 35.496 159.431 36.816C160.175 38.136 160.547 39.672 160.547 41.424C160.547 41.808 160.523 42.204 160.475 42.612C160.451 43.02 160.415 43.344 160.367 43.584H148.919C149.039 44.52 149.303 45.132 149.711 45.42C150.119 45.708 150.683 45.852 151.403 45.852C152.579 45.852 153.299 45.336 153.563 44.304L160.223 46.104C159.959 47.28 159.371 48.264 158.459 49.056C157.547 49.824 156.455 50.4 155.183 50.784C153.935 51.168 152.639 51.36 151.295 51.36ZM151.007 38.112C150.383 38.112 149.903 38.292 149.567 38.652C149.255 38.988 149.039 39.612 148.919 40.524H152.807C152.735 39.732 152.567 39.132 152.303 38.724C152.039 38.316 151.607 38.112 151.007 38.112ZM172.322 39.768C172.322 38.592 171.806 38.004 170.774 38.004C170.27 38.004 169.85 38.148 169.514 38.436C169.178 38.724 168.986 39.3 168.938 40.164L161.954 39.156C162.122 37.164 162.986 35.58 164.546 34.404C166.106 33.228 168.374 32.64 171.35 32.64C174.47 32.64 176.774 33.276 178.262 34.548C179.774 35.796 180.53 37.452 180.53 39.516V45.096C180.53 46.08 180.998 46.572 181.934 46.572C182.198 46.572 182.402 46.536 182.546 46.464L182.186 50.712C181.298 51.144 180.23 51.36 178.982 51.36C177.59 51.36 176.402 51.12 175.418 50.64C174.434 50.16 173.774 49.428 173.438 48.444C172.958 49.308 172.202 50.016 171.17 50.568C170.138 51.096 168.962 51.36 167.642 51.36C165.746 51.36 164.282 50.94 163.25 50.1C162.242 49.236 161.738 48.06 161.738 46.572C161.738 45.252 162.158 44.184 162.998 43.368C163.838 42.528 165.05 41.952 166.634 41.64L172.322 40.452V39.768ZM169.514 45.348C169.514 45.732 169.634 46.044 169.874 46.284C170.114 46.524 170.45 46.644 170.882 46.644C171.29 46.644 171.626 46.5 171.89 46.212C172.178 45.924 172.322 45.516 172.322 44.988V43.404L170.918 43.728C169.982 43.968 169.514 44.508 169.514 45.348ZM183.73 51V33H191.182L191.362 36.924C191.89 35.556 192.682 34.5 193.738 33.756C194.794 33.012 196.09 32.64 197.626 32.64C199.402 32.64 200.782 33.168 201.766 34.224C202.774 35.28 203.278 36.768 203.278 38.688V51H194.998V40.74C194.998 39.972 194.89 39.456 194.674 39.192C194.458 38.904 194.134 38.76 193.702 38.76C193.102 38.76 192.67 38.976 192.406 39.408C192.142 39.816 192.01 40.512 192.01 41.496V51H183.73ZM228.95 27.24V40.776C228.95 47.832 225.122 51.36 217.466 51.36C209.81 51.36 205.982 47.832 205.982 40.776V27.24H214.406V41.424C214.406 43.848 215.426 45.06 217.466 45.06C219.53 45.06 220.562 43.848 220.562 41.424V27.24H228.95ZM231.683 58.56V33H239.207L239.387 36.6C239.891 35.4 240.623 34.44 241.583 33.72C242.543 33 243.707 32.64 245.075 32.64C247.283 32.64 248.975 33.456 250.151 35.088C251.351 36.696 251.951 38.964 251.951 41.892C251.951 44.892 251.351 47.22 250.151 48.876C248.951 50.532 247.247 51.36 245.039 51.36C243.791 51.36 242.747 51.06 241.907 50.46C241.067 49.836 240.419 49.02 239.963 48.012V58.56H231.683ZM241.727 45.528C242.303 45.528 242.759 45.3 243.095 44.844C243.431 44.364 243.599 43.416 243.599 42C243.599 40.584 243.431 39.636 243.095 39.156C242.759 38.676 242.303 38.436 241.727 38.436C241.175 38.436 240.743 38.724 240.431 39.3C240.119 39.852 239.963 40.752 239.963 42C239.963 44.352 240.551 45.528 241.727 45.528ZM257.901 43.584C259.317 43.584 260.409 43.92 261.177 44.592C261.945 45.264 262.329 46.212 262.329 47.436C262.329 48.66 261.945 49.608 261.177 50.28C260.409 50.952 259.317 51.288 257.901 51.288C256.509 51.288 255.417 50.952 254.625 50.28C253.857 49.608 253.473 48.66 253.473 47.436C253.473 46.212 253.857 45.264 254.625 44.592C255.417 43.92 256.509 43.584 257.901 43.584ZM264.801 58.56V33H272.325L272.505 36.6C273.009 35.4 273.741 34.44 274.701 33.72C275.661 33 276.825 32.64 278.193 32.64C280.401 32.64 282.093 33.456 283.269 35.088C284.469 36.696 285.069 38.964 285.069 41.892C285.069 44.892 284.469 47.22 283.269 48.876C282.069 50.532 280.365 51.36 278.157 51.36C276.909 51.36 275.865 51.06 275.025 50.46C274.185 49.836 273.537 49.02 273.081 48.012V58.56H264.801ZM274.845 45.528C275.421 45.528 275.877 45.3 276.213 44.844C276.549 44.364 276.717 43.416 276.717 42C276.717 40.584 276.549 39.636 276.213 39.156C275.877 38.676 275.421 38.436 274.845 38.436C274.293 38.436 273.861 38.724 273.549 39.3C273.237 39.852 273.081 40.752 273.081 42C273.081 44.352 273.669 45.528 274.845 45.528ZM291.511 31.848C289.879 31.848 288.715 31.584 288.019 31.056C287.347 30.528 287.011 29.64 287.011 28.392C287.011 27.12 287.347 26.232 288.019 25.728C288.715 25.2 289.879 24.936 291.511 24.936C293.143 24.936 294.295 25.2 294.967 25.728C295.663 26.232 296.011 27.12 296.011 28.392C296.011 29.64 295.663 30.528 294.967 31.056C294.295 31.584 293.143 31.848 291.511 31.848ZM295.651 33V51H287.371V33H295.651ZM307.733 32.64C309.941 32.64 311.741 32.952 313.133 33.576C314.525 34.176 315.545 34.944 316.193 35.88C316.865 36.816 317.213 37.776 317.237 38.76L309.605 40.992C309.605 40.032 309.485 39.336 309.245 38.904C309.029 38.448 308.645 38.22 308.093 38.22C307.373 38.22 306.857 38.496 306.545 39.048C306.233 39.6 306.077 40.62 306.077 42.108C306.077 43.14 306.161 43.92 306.329 44.448C306.521 44.976 306.773 45.336 307.085 45.528C307.397 45.696 307.745 45.78 308.129 45.78C309.089 45.78 309.629 44.988 309.749 43.404L317.165 45.708C317.141 46.716 316.757 47.652 316.013 48.516C315.269 49.356 314.225 50.04 312.881 50.568C311.561 51.096 309.989 51.36 308.165 51.36C304.901 51.36 302.345 50.544 300.497 48.912C298.673 47.28 297.761 44.976 297.761 42C297.761 40.008 298.169 38.316 298.985 36.924C299.825 35.532 300.989 34.476 302.477 33.756C303.989 33.012 305.741 32.64 307.733 32.64ZM333.303 33V38.58H328.767V43.656C328.767 44.472 328.935 45.06 329.271 45.42C329.631 45.756 330.183 45.924 330.927 45.924C331.815 45.924 332.571 45.768 333.195 45.456L333.555 50.136C332.955 50.52 332.139 50.82 331.107 51.036C330.075 51.252 329.103 51.36 328.191 51.36C325.671 51.36 323.751 50.88 322.431 49.92C321.135 48.936 320.487 47.28 320.487 44.952V38.58H317.715V33H320.487V29.184L328.767 26.88V33H333.303ZM340.979 51.36C339.107 51.36 337.667 50.88 336.659 49.92C335.651 48.936 335.147 47.352 335.147 45.168V33H343.427V43.26C343.427 43.956 343.535 44.46 343.751 44.772C343.991 45.084 344.339 45.24 344.795 45.24C345.875 45.24 346.415 44.34 346.415 42.54V33H354.695V51H347.243L347.063 47.184C346.007 49.968 343.979 51.36 340.979 51.36ZM357.508 51V33H364.96L365.14 37.5C365.548 36.012 366.172 34.836 367.012 33.972C367.876 33.084 369.016 32.64 370.432 32.64C370.984 32.64 371.404 32.688 371.692 32.784C371.98 32.88 372.184 32.976 372.304 33.072L371.62 39.732C371.404 39.636 371.092 39.552 370.684 39.48C370.276 39.408 369.832 39.372 369.352 39.372C368.272 39.372 367.408 39.624 366.76 40.128C366.112 40.632 365.788 41.376 365.788 42.36V51H357.508ZM383.116 51.36C379.852 51.36 377.32 50.544 375.52 48.912C373.72 47.28 372.82 44.976 372.82 42C372.82 40.008 373.228 38.316 374.044 36.924C374.884 35.532 376.048 34.476 377.536 33.756C379.048 33.012 380.8 32.64 382.792 32.64C384.832 32.64 386.56 33.012 387.976 33.756C389.416 34.476 390.508 35.496 391.252 36.816C391.996 38.136 392.368 39.672 392.368 41.424C392.368 41.808 392.344 42.204 392.296 42.612C392.272 43.02 392.236 43.344 392.188 43.584H380.74C380.86 44.52 381.124 45.132 381.532 45.42C381.94 45.708 382.504 45.852 383.224 45.852C384.4 45.852 385.12 45.336 385.384 44.304L392.044 46.104C391.78 47.28 391.192 48.264 390.28 49.056C389.368 49.824 388.276 50.4 387.004 50.784C385.756 51.168 384.46 51.36 383.116 51.36ZM382.828 38.112C382.204 38.112 381.724 38.292 381.388 38.652C381.076 38.988 380.86 39.612 380.74 40.524H384.628C384.556 39.732 384.388 39.132 384.124 38.724C383.86 38.316 383.428 38.112 382.828 38.112ZM402.666 51.36C400.914 51.36 399.222 51.12 397.59 50.64C395.982 50.16 394.578 49.44 393.378 48.48L396.33 44.196C396.978 44.772 397.83 45.288 398.886 45.744C399.942 46.176 400.998 46.392 402.054 46.392C402.462 46.392 402.822 46.344 403.134 46.248C403.47 46.152 403.638 45.984 403.638 45.744C403.638 45.552 403.542 45.408 403.35 45.312C403.182 45.216 402.798 45.12 402.198 45.024L400.902 44.772C398.286 44.268 396.438 43.548 395.358 42.612C394.278 41.676 393.738 40.416 393.738 38.832C393.738 37.848 394.05 36.888 394.674 35.952C395.322 34.992 396.342 34.2 397.734 33.576C399.126 32.952 400.962 32.64 403.242 32.64C405.114 32.64 406.794 32.88 408.282 33.36C409.77 33.816 410.982 34.452 411.918 35.268L409.074 39.372C408.402 38.844 407.538 38.424 406.482 38.112C405.45 37.776 404.502 37.608 403.638 37.608C403.038 37.608 402.618 37.668 402.378 37.788C402.138 37.884 402.018 38.016 402.018 38.184C402.018 38.352 402.138 38.508 402.378 38.652C402.618 38.796 403.086 38.928 403.782 39.048L406.338 39.48C408.282 39.792 409.698 40.416 410.586 41.352C411.498 42.288 411.954 43.44 411.954 44.808C411.954 45.984 411.63 47.076 410.982 48.084C410.358 49.068 409.362 49.86 407.994 50.46C406.65 51.06 404.874 51.36 402.666 51.36Z"
|
13 |
+
fill="black"
|
14 |
+
/>
|
15 |
+
<path
|
16 |
+
d="M32.3784 6C24.8503 12.4687 17.1994 18.7776 9.57114 25.1287C9.21086 25.4287 3.51469 29.0986 7.28323 28.7435C11.1275 28.3813 14.9766 26.7755 18.5431 25.4487C26.9472 22.3224 34.9705 18.3498 43.231 14.8888C45.234 14.0496 47.5435 13.1384 49.6037 12.3525C50.3249 12.0774 51.0159 11.658 51.7838 11.5703C52.1318 11.5306 51.2979 12.0741 51.0171 12.2814C47.8543 14.6172 44.5735 16.8155 41.3384 19.0488C33.1146 24.726 24.783 30.251 16.5667 35.9375C15.472 36.6951 12.3341 38.7486 13.6678 38.6753C16.281 38.5316 19.1152 37.518 21.4899 36.542C29.0864 33.4195 35.8808 28.6199 42.8956 24.4413C46.0358 22.5708 49.2021 20.7546 52.4306 19.0369C55.0217 17.6584 57.9194 15.9401 60.7917 15.114C61.8487 14.81 61.15 15.3907 60.6959 15.8488C57.4898 19.0831 53.7876 22.0277 50.2266 24.8443C40.6129 32.448 30.4389 39.306 20.0644 45.8456C17.3593 47.5508 13.2575 50.0488 10.3138 51.7477C9.42941 52.2582 6.68112 53.6127 7.61863 53.2055C14.5146 50.2101 21.3676 47.122 28.114 43.8071C40.6576 37.6437 52.857 30.8108 65.691 25.2354C67.4153 24.4863 70.7591 23.0521 72.8422 22.3791C74.5883 21.815 73.5446 22.4481 72.5068 23.1732C61.8057 30.6501 50.5857 37.5569 39.4218 44.3286C31.8827 48.9015 24.2212 53.2329 16.1594 56.844C15.0664 57.3336 13.9891 57.8913 12.8293 58.1951C10.964 58.6836 16.4296 56.8193 18.2437 56.1684C24.6432 53.8723 30.8867 51.2692 37.1699 48.6781C49.9855 43.3932 62.8729 38.4374 76.1722 34.4679C78.1078 33.8902 80.0538 33.3477 81.9938 32.7849C82.8974 32.5228 83.4275 32.2167 82.0418 32.8798C79.515 34.0888 78.032 34.972 75.3337 36.5657C64.6559 42.8721 54.1878 49.5186 43.4946 55.801C33.8109 61.4904 23.6948 68.0711 12.9491 71.7535C12.4873 71.9118 11.0878 72.15 11.4877 71.872C14.746 69.6074 20.0543 68.1178 23.5142 66.7402C40.3001 60.0571 56.9851 53.0797 73.8723 46.6515C76.9827 45.4675 84.3981 42.7403 87.8394 41.6738C89.2613 41.2331 96.1589 38.9157 91.2773 42.776C82.9402 49.3687 72.7936 54.14 63.2234 58.6573C59.2228 60.5456 55.316 62.3148 51.1609 63.801C50.0671 64.1922 48.9511 64.5226 47.8308 64.8321C46.6536 65.1573 45.3402 65.0935 44.2612 65.6617C43.2596 66.1892 46.5326 65.6388 47.6631 65.5432C53.7394 65.0292 59.7854 64.2771 65.7868 63.1965C72.0428 62.0701 78.2017 60.5262 84.3417 58.9062"
|
17 |
+
stroke="#BDFF01"
|
18 |
+
strokeWidth="12"
|
19 |
+
strokeLinecap="round"
|
20 |
+
/>
|
21 |
+
<path
|
22 |
+
d="M27.119 50.2256L47.4582 28.0373H74.5772L48.6909 65.6341H22.1882L27.119 50.2256Z"
|
23 |
+
fill="white"
|
24 |
+
stroke="black"
|
25 |
+
/>
|
26 |
+
<path
|
27 |
+
d="M14.2947 52.6267L7.70887 53.7462C6.95087 53.8756 6.4398 54.5932 6.57013 55.3519C6.63106 55.7295 6.84414 56.0464 7.13358 56.2509C7.42443 56.4591 7.79561 56.5551 8.17544 56.4931L14.7619 55.3727C15.5192 55.2442 16.0316 54.5247 15.903 53.7674C15.7749 53.0076 15.0547 52.5001 14.2947 52.6267Z"
|
28 |
+
fill="black"
|
29 |
+
/>
|
30 |
+
<path
|
31 |
+
d="M55.3193 20.5792C55.6276 20.3977 55.8663 20.0982 55.9623 19.7239L57.6224 13.2509C57.8124 12.5073 57.3623 11.7474 56.6197 11.5569C55.8747 11.3666 55.1151 11.8152 54.9257 12.5597L53.2656 19.0327C53.0738 19.7774 53.5247 20.5343 54.2684 20.7267C54.6391 20.821 55.0149 20.7585 55.3193 20.5792Z"
|
32 |
+
fill="black"
|
33 |
+
/>
|
34 |
+
<path
|
35 |
+
d="M32.277 23.1873C32.6333 23.8673 33.4747 24.1301 34.1544 23.7727C34.8366 23.4159 35.0983 22.5747 34.7428 21.8934L31.6392 15.9762C31.2805 15.2956 30.4413 15.0324 29.7572 15.3907C29.0755 15.7497 28.8158 16.5893 29.1734 17.2701L32.277 23.1873Z"
|
36 |
+
fill="black"
|
37 |
+
/>
|
38 |
+
<path
|
39 |
+
d="M13.4455 34.9526L19.7321 37.2162C20.4539 37.477 21.2501 37.1029 21.5146 36.3781C21.7756 35.6542 21.4004 34.8578 20.6742 34.5966L14.3901 32.331C13.6662 32.0689 12.8697 32.4452 12.6044 33.1676C12.3445 33.8917 12.7216 34.6915 13.4455 34.9526Z"
|
40 |
+
fill="black"
|
41 |
+
/>
|
42 |
+
<path
|
43 |
+
d="M76.1485 26.0102C75.8931 25.6802 75.4948 25.4828 75.0771 25.4828H48.1771C47.9337 25.4828 47.7014 25.5502 47.4949 25.6774L47.4478 25.6377L24.7798 49.603L24.5382 49.875L24.5687 49.9064C24.518 49.9949 24.4802 50.0861 24.4553 50.1811L20.2501 65.8743C20.144 66.2772 20.2307 66.7142 20.4824 67.0452C20.7378 67.3753 21.1379 67.5726 21.5538 67.5726H48.4556C48.8862 67.5726 49.2679 67.391 49.6643 66.9945L71.7884 43.5575C71.8603 43.481 72.1074 43.1279 72.1756 42.8743L76.3808 27.1802C76.4887 26.7782 76.402 26.3431 76.1485 26.0102ZM47.4183 64.873H23.312L26.7944 51.8813H50.9007L47.4183 64.873ZM52.2929 49.1817H28.8928L48.7589 28.1824H72.1563L52.2929 49.1817Z"
|
44 |
+
fill="black"
|
45 |
+
/>
|
46 |
+
</svg>
|
47 |
+
)
|
48 |
+
}
|
src/components/MadeWidthBadge.tsx
ADDED
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
export default function MadeWidthBadge() {
|
2 |
+
return (
|
3 |
+
<svg
|
4 |
+
width="101"
|
5 |
+
height="43"
|
6 |
+
viewBox="0 0 101 43"
|
7 |
+
fill="none"
|
8 |
+
xmlns="http://www.w3.org/2000/svg"
|
9 |
+
>
|
10 |
+
<path
|
11 |
+
fillRule="evenodd"
|
12 |
+
clipRule="evenodd"
|
13 |
+
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"
|
14 |
+
fill="black"
|
15 |
+
/>
|
16 |
+
<path
|
17 |
+
fillRule="evenodd"
|
18 |
+
clipRule="evenodd"
|
19 |
+
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"
|
20 |
+
fill="black"
|
21 |
+
/>
|
22 |
+
<path
|
23 |
+
fillRule="evenodd"
|
24 |
+
clipRule="evenodd"
|
25 |
+
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"
|
26 |
+
fill="black"
|
27 |
+
/>
|
28 |
+
<path
|
29 |
+
fillRule="evenodd"
|
30 |
+
clipRule="evenodd"
|
31 |
+
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"
|
32 |
+
fill="black"
|
33 |
+
/>
|
34 |
+
<path
|
35 |
+
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"
|
36 |
+
fill="black"
|
37 |
+
/>
|
38 |
+
<path
|
39 |
+
d="M41.1319 25.0389H38.7905V38.2852H41.1319V25.0389Z"
|
40 |
+
fill="black"
|
41 |
+
/>
|
42 |
+
<path
|
43 |
+
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"
|
44 |
+
fill="black"
|
45 |
+
/>
|
46 |
+
<path
|
47 |
+
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"
|
48 |
+
fill="black"
|
49 |
+
/>
|
50 |
+
<path
|
51 |
+
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"
|
52 |
+
fill="black"
|
53 |
+
/>
|
54 |
+
<path
|
55 |
+
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"
|
56 |
+
fill="black"
|
57 |
+
/>
|
58 |
+
<path
|
59 |
+
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"
|
60 |
+
fill="black"
|
61 |
+
/>
|
62 |
+
<path
|
63 |
+
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"
|
64 |
+
fill="black"
|
65 |
+
/>
|
66 |
+
<path
|
67 |
+
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"
|
68 |
+
fill="black"
|
69 |
+
/>
|
70 |
+
</svg>
|
71 |
+
)
|
72 |
+
}
|
src/components/Modal.tsx
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { ReactNode } from 'react'
|
2 |
+
|
3 |
+
interface ModalProps {
|
4 |
+
children?: ReactNode
|
5 |
+
}
|
6 |
+
|
7 |
+
export default function Modal(props: ModalProps) {
|
8 |
+
const { children } = props
|
9 |
+
return (
|
10 |
+
<div
|
11 |
+
className={[
|
12 |
+
'absolute w-full h-full flex justify-center items-center',
|
13 |
+
'bg-white bg-opacity-40 backdrop-filter backdrop-blur-md',
|
14 |
+
].join(' ')}
|
15 |
+
>
|
16 |
+
<div className="bg-primary p-16 max-w-4xl">{children}</div>
|
17 |
+
</div>
|
18 |
+
)
|
19 |
+
}
|
src/components/Progress.tsx
ADDED
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
interface ProgressProps {
|
2 |
+
percent: number
|
3 |
+
}
|
4 |
+
|
5 |
+
export default function Progress({ percent }: ProgressProps) {
|
6 |
+
return (
|
7 |
+
<div className="w-full flex items-center">
|
8 |
+
<div className="relative flex-1 bg-black/20 h-2 mr-4">
|
9 |
+
<div
|
10 |
+
className="absolute left-0 top-0 h-full bg-black duration-100"
|
11 |
+
style={{ width: `${percent}%` }}
|
12 |
+
/>
|
13 |
+
</div>
|
14 |
+
<span className="w-20 text-right">{percent.toFixed(2)}%</span>
|
15 |
+
</div>
|
16 |
+
)
|
17 |
+
}
|
src/components/Slider.tsx
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
type SliderProps = {
|
2 |
+
label?: any
|
3 |
+
value?: number
|
4 |
+
min?: number
|
5 |
+
max?: number
|
6 |
+
onChange: (value: number) => void
|
7 |
+
onStart?: () => void
|
8 |
+
}
|
9 |
+
|
10 |
+
export default function Slider(props: SliderProps) {
|
11 |
+
const { value, label, min, max, onChange, onStart } = props
|
12 |
+
|
13 |
+
const step = ((max || 100) - (min || 0)) / 100
|
14 |
+
|
15 |
+
return (
|
16 |
+
<div className="inline-flex items-center space-x-4 text-black">
|
17 |
+
<span>{label}</span>
|
18 |
+
<input
|
19 |
+
className={['appearance-none rounded-lg h-4', 'bg-primary'].join(' ')}
|
20 |
+
type="range"
|
21 |
+
step={step}
|
22 |
+
min={min}
|
23 |
+
max={max}
|
24 |
+
value={value}
|
25 |
+
onPointerDown={onStart}
|
26 |
+
onChange={ev => {
|
27 |
+
ev.preventDefault()
|
28 |
+
ev.stopPropagation()
|
29 |
+
onChange(parseInt(ev.currentTarget.value, 10))
|
30 |
+
}}
|
31 |
+
/>
|
32 |
+
</div>
|
33 |
+
)
|
34 |
+
}
|
src/index.css
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
@tailwind base;
|
2 |
+
@tailwind components;
|
3 |
+
@tailwind utilities;
|
src/index.tsx
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import ReactDOM from 'react-dom'
|
2 |
+
import './index.css'
|
3 |
+
import App from './App'
|
4 |
+
import { loadingOnnxruntime } from './adapters/util'
|
5 |
+
|
6 |
+
loadingOnnxruntime()
|
7 |
+
|
8 |
+
ReactDOM.render(<App />, document.getElementById('root'))
|
src/react-app-env.d.ts
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
/// <reference types="react-scripts" />
|
src/setupTests.ts
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
2 |
+
// allows you to do things like:
|
3 |
+
// expect(element).toHaveTextContent(/react/i)
|
4 |
+
// learn more: https://github.com/testing-library/jest-dom
|
5 |
+
import '@testing-library/jest-dom/extend-expect'
|
src/utils.ts
ADDED
@@ -0,0 +1,159 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useCallback, useEffect, useState } from 'react'
|
2 |
+
|
3 |
+
export function dataURItoBlob(dataURI: string) {
|
4 |
+
const mime = dataURI.split(',')[0].split(':')[1].split(';')[0]
|
5 |
+
const binary = atob(dataURI.split(',')[1])
|
6 |
+
const array = []
|
7 |
+
for (let i = 0; i < binary.length; i += 1) {
|
8 |
+
array.push(binary.charCodeAt(i))
|
9 |
+
}
|
10 |
+
return new Blob([new Uint8Array(array)], { type: mime })
|
11 |
+
}
|
12 |
+
|
13 |
+
// const dataURItoBlob = (dataURI: string) => {
|
14 |
+
// const bytes =
|
15 |
+
// dataURI.split(',')[0].indexOf('base64') >= 0
|
16 |
+
// ? atob(dataURI.split(',')[1])
|
17 |
+
// : unescape(dataURI.split(',')[1])
|
18 |
+
// const mime = dataURI.split(',')[0].split(':')[1].split(';')[0]
|
19 |
+
// const max = bytes.length
|
20 |
+
// const ia = new Uint8Array(max)
|
21 |
+
// for (var i = 0; i < max; i++) ia[i] = bytes.charCodeAt(i)
|
22 |
+
// return new Blob([ia], { type: mime })
|
23 |
+
// }
|
24 |
+
|
25 |
+
export function downloadImage(uri: string, name: string) {
|
26 |
+
const link = document.createElement('a')
|
27 |
+
link.href = uri
|
28 |
+
link.download = name
|
29 |
+
|
30 |
+
// this is necessary as link.click() does not work on the latest firefox
|
31 |
+
link.dispatchEvent(
|
32 |
+
new MouseEvent('click', {
|
33 |
+
bubbles: true,
|
34 |
+
cancelable: true,
|
35 |
+
view: window,
|
36 |
+
})
|
37 |
+
)
|
38 |
+
|
39 |
+
setTimeout(() => {
|
40 |
+
// For Firefox it is necessary to delay revoking the ObjectURL
|
41 |
+
// window.URL.revokeObjectURL(base64)
|
42 |
+
link.remove()
|
43 |
+
}, 100)
|
44 |
+
}
|
45 |
+
|
46 |
+
export function loadImage(image: HTMLImageElement, src: string) {
|
47 |
+
return new Promise((resolve, reject) => {
|
48 |
+
const initSRC = image.src
|
49 |
+
const img = image
|
50 |
+
img.onload = resolve
|
51 |
+
img.onerror = err => {
|
52 |
+
img.src = initSRC
|
53 |
+
reject(err)
|
54 |
+
}
|
55 |
+
img.src = src
|
56 |
+
})
|
57 |
+
}
|
58 |
+
|
59 |
+
export function useImage(
|
60 |
+
file: Blob | MediaSource
|
61 |
+
): [HTMLImageElement, boolean, (width: number, height: number) => void] {
|
62 |
+
const [image, setImage] = useState(new Image())
|
63 |
+
const [isLoaded, setIsLoaded] = useState(false)
|
64 |
+
|
65 |
+
// 调整图像分辨率的函数
|
66 |
+
const adjustResolution = useCallback(
|
67 |
+
(width, height) => {
|
68 |
+
const canvas = document.createElement('canvas')
|
69 |
+
const context = canvas.getContext('2d')!
|
70 |
+
canvas.width = width
|
71 |
+
canvas.height = height
|
72 |
+
context.drawImage(image, 0, 0, width, height)
|
73 |
+
const resizedImage = new Image()
|
74 |
+
resizedImage.src = canvas.toDataURL()
|
75 |
+
setImage(resizedImage)
|
76 |
+
},
|
77 |
+
[image]
|
78 |
+
)
|
79 |
+
|
80 |
+
useEffect(() => {
|
81 |
+
const newImage = new Image()
|
82 |
+
newImage.onload = () => {
|
83 |
+
setIsLoaded(true)
|
84 |
+
}
|
85 |
+
newImage.src = URL.createObjectURL(file)
|
86 |
+
setImage(newImage)
|
87 |
+
|
88 |
+
return () => {
|
89 |
+
newImage.onload = null
|
90 |
+
}
|
91 |
+
}, [file])
|
92 |
+
|
93 |
+
return [image, isLoaded, adjustResolution]
|
94 |
+
}
|
95 |
+
|
96 |
+
// https://stackoverflow.com/questions/23945494/use-html5-to-resize-an-image-before-upload
|
97 |
+
interface ResizeImageFileResult {
|
98 |
+
file: File
|
99 |
+
resized: boolean
|
100 |
+
originalWidth?: number
|
101 |
+
originalHeight?: number
|
102 |
+
}
|
103 |
+
export function resizeImageFile(
|
104 |
+
file: File,
|
105 |
+
maxSize: number
|
106 |
+
): Promise<ResizeImageFileResult> {
|
107 |
+
const reader = new FileReader()
|
108 |
+
const image = new Image()
|
109 |
+
const canvas = document.createElement('canvas')
|
110 |
+
|
111 |
+
const resize = (): ResizeImageFileResult => {
|
112 |
+
let { width, height } = image
|
113 |
+
|
114 |
+
if (width > height) {
|
115 |
+
if (width > maxSize) {
|
116 |
+
height *= maxSize / width
|
117 |
+
width = maxSize
|
118 |
+
}
|
119 |
+
} else if (height > maxSize) {
|
120 |
+
width *= maxSize / height
|
121 |
+
height = maxSize
|
122 |
+
}
|
123 |
+
|
124 |
+
if (width === image.width && height === image.height) {
|
125 |
+
return { file, resized: false }
|
126 |
+
}
|
127 |
+
|
128 |
+
canvas.width = width
|
129 |
+
canvas.height = height
|
130 |
+
const ctx = canvas.getContext('2d')
|
131 |
+
if (!ctx) {
|
132 |
+
throw new Error('could not get context')
|
133 |
+
}
|
134 |
+
canvas.getContext('2d')?.drawImage(image, 0, 0, width, height)
|
135 |
+
const dataUrl = canvas.toDataURL('image/jpeg')
|
136 |
+
const blob = dataURItoBlob(dataUrl)
|
137 |
+
const f = new File([blob], file.name, {
|
138 |
+
type: file.type,
|
139 |
+
})
|
140 |
+
return {
|
141 |
+
file: f,
|
142 |
+
resized: true,
|
143 |
+
originalWidth: image.width,
|
144 |
+
originalHeight: image.height,
|
145 |
+
}
|
146 |
+
}
|
147 |
+
|
148 |
+
return new Promise((resolve, reject) => {
|
149 |
+
if (!file.type.match(/image.*/)) {
|
150 |
+
reject(new Error('Not an image'))
|
151 |
+
return
|
152 |
+
}
|
153 |
+
reader.onload = (readerEvent: any) => {
|
154 |
+
image.onload = () => resolve(resize())
|
155 |
+
image.src = readerEvent.target.result
|
156 |
+
}
|
157 |
+
reader.readAsDataURL(file)
|
158 |
+
})
|
159 |
+
}
|
src/vite-env.d.ts
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
/// <reference types="vite/client" />
|
tailwind.config.js
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const tailwindScrollbar = require('tailwind-scrollbar')
|
2 |
+
|
3 |
+
/** @type {import('tailwindcss').Config} */
|
4 |
+
module.exports = {
|
5 |
+
content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'],
|
6 |
+
theme: {
|
7 |
+
extend: {
|
8 |
+
animation: {
|
9 |
+
'pulse-fast': 'pulse 0.7s cubic-bezier(0.4, 0, 0.6, 1) infinite',
|
10 |
+
},
|
11 |
+
colors: {
|
12 |
+
primary: '#BDFF01',
|
13 |
+
},
|
14 |
+
},
|
15 |
+
},
|
16 |
+
variants: {},
|
17 |
+
plugins: [tailwindScrollbar],
|
18 |
+
}
|
tsconfig.json
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"compilerOptions": {
|
3 |
+
"target": "ESNext",
|
4 |
+
"lib": ["dom", "dom.iterable", "esnext"],
|
5 |
+
"allowJs": true,
|
6 |
+
"skipLibCheck": true,
|
7 |
+
"esModuleInterop": true,
|
8 |
+
"allowSyntheticDefaultImports": true,
|
9 |
+
"strict": true,
|
10 |
+
"forceConsistentCasingInFileNames": true,
|
11 |
+
"module": "esnext",
|
12 |
+
"moduleResolution": "node",
|
13 |
+
"resolveJsonModule": true,
|
14 |
+
"isolatedModules": true,
|
15 |
+
"noEmit": true,
|
16 |
+
"noFallthroughCasesInSwitch": true,
|
17 |
+
"jsx": "react-jsx",
|
18 |
+
"types": ["vite/client", "vite-plugin-svgr/client"]
|
19 |
+
},
|
20 |
+
"include": ["src"]
|
21 |
+
}
|
vite.config.ts
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { defineConfig } from 'vitest/config'
|
2 |
+
import react from '@vitejs/plugin-react-swc'
|
3 |
+
import eslintPlugin from 'vite-plugin-eslint'
|
4 |
+
|
5 |
+
// https://vitejs.dev/config/
|
6 |
+
export default defineConfig({
|
7 |
+
base: '/',
|
8 |
+
plugins: [react(), eslintPlugin()],
|
9 |
+
test: {
|
10 |
+
globals: true,
|
11 |
+
environment: 'jsdom',
|
12 |
+
setupFiles: './src/setupTests.ts',
|
13 |
+
css: true,
|
14 |
+
reporters: ['verbose'],
|
15 |
+
coverage: {
|
16 |
+
reporter: ['text', 'json', 'html'],
|
17 |
+
include: ['src/**/*'],
|
18 |
+
exclude: [],
|
19 |
+
},
|
20 |
+
},
|
21 |
+
server: {
|
22 |
+
headers: {
|
23 |
+
'Cross-Origin-Opener-Policy': 'same-origin',
|
24 |
+
'Cross-Origin-Embedder-Policy': 'require-corp',
|
25 |
+
},
|
26 |
+
},
|
27 |
+
})
|