Xu Ma commited on
Commit
28958dc
1 Parent(s): b60c0af

upload all files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
.DS_Store ADDED
Binary file (8.2 kB). View file
 
CMakeLists.txt ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ cmake_minimum_required(VERSION 3.12)
2
+
3
+ project(diffvg VERSION 0.0.1 DESCRIPTION "Differentiable Vector Graphics")
4
+
5
+ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/")
6
+ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
7
+
8
+ if(WIN32)
9
+ find_package(Python 3.6 COMPONENTS Development REQUIRED)
10
+ else()
11
+ find_package(Python 3.7 COMPONENTS Development REQUIRED)
12
+ endif()
13
+ add_subdirectory(pybind11)
14
+
15
+ option(DIFFVG_CUDA "Build diffvg with GPU code path?" ON)
16
+
17
+ if(DIFFVG_CUDA)
18
+ message(STATUS "Build with CUDA support")
19
+ find_package(CUDA 10 REQUIRED)
20
+ set(CMAKE_CUDA_STANDARD 11)
21
+ if(NOT WIN32)
22
+ # Hack: for some reason the line above doesn't work on some Linux systems.
23
+ set(CUDA_NVCC_FLAGS "${CUDA_NVCC_FLAGS} -std=c++11")
24
+ #set(CUDA_NVCC_FLAGS_DEBUG "-g -G")
25
+ endif()
26
+ else()
27
+ message(STATUS "Build without CUDA support")
28
+ find_package(Thrust REQUIRED)
29
+ endif()
30
+
31
+ # include_directories(${CMAKE_SOURCE_DIR}/pybind11/include)
32
+ include_directories(${PYTHON_INCLUDE_PATH})
33
+ find_package(PythonLibs REQUIRED)
34
+ include_directories(${PYTHON_INCLUDE_PATH})
35
+ include_directories(${PYTHON_INCLUDE_DIRS})
36
+ include_directories(pybind11/include)
37
+ if(DIFFVG_CUDA)
38
+ link_directories(${CUDA_LIBRARIES})
39
+ else()
40
+ include_directories(${THRUST_INCLUDE_DIR})
41
+ endif()
42
+
43
+ if(NOT MSVC)
44
+ # These compile definitions are not meaningful for MSVC
45
+ add_compile_options(-Wall -g -O3 -fvisibility=hidden -Wno-unknown-pragmas)
46
+ else()
47
+ add_compile_options(/Wall /Zi)
48
+ add_link_options(/DEBUG)
49
+ endif()
50
+
51
+ if(NOT DIFFVG_CUDA)
52
+ add_compile_options("-DTHRUST_DEVICE_SYSTEM=THRUST_DEVICE_SYSTEM_CPP")
53
+ endif()
54
+
55
+ set(SRCS atomic.h
56
+ color.h
57
+ cdf.h
58
+ cuda_utils.h
59
+ diffvg.h
60
+ edge_query.h
61
+ filter.h
62
+ matrix.h
63
+ parallel.h
64
+ pcg.h
65
+ ptr.h
66
+ sample_boundary.h
67
+ scene.h
68
+ shape.h
69
+ solve.h
70
+ vector.h
71
+ within_distance.h
72
+ winding_number.h
73
+ atomic.cpp
74
+ color.cpp
75
+ diffvg.cpp
76
+ parallel.cpp
77
+ scene.cpp
78
+ shape.cpp)
79
+
80
+ if(DIFFVG_CUDA)
81
+ add_compile_definitions(COMPILE_WITH_CUDA)
82
+ set_source_files_properties(
83
+ diffvg.cpp
84
+ scene.cpp
85
+ PROPERTIES CUDA_SOURCE_PROPERTY_FORMAT OBJ)
86
+
87
+ cuda_add_library(diffvg MODULE ${SRCS})
88
+ else()
89
+ add_library(diffvg MODULE ${SRCS})
90
+ endif()
91
+
92
+ if(APPLE)
93
+ # The "-undefined dynamic_lookup" is a hack for systems with
94
+ # multiple Python installed. If we link a particular Python version
95
+ # here, and we import it with a different Python version later.
96
+ # likely a segmentation fault.
97
+ # The solution for Linux Mac OS machines, as mentioned in
98
+ # https://github.com/pybind/pybind11/blob/master/tools/pybind11Tools.cmake
99
+ # is to not link against Python library at all and resolve the symbols
100
+ # at compile time.
101
+ set(DYNAMIC_LOOKUP "-undefined dynamic_lookup")
102
+ endif()
103
+
104
+ target_link_libraries(diffvg ${DYNAMIC_LOOKUP})
105
+
106
+ if(WIN32)
107
+ # See: https://pybind11.readthedocs.io/en/master/compiling.html#advanced-interface-library-target
108
+ target_link_libraries(diffvg pybind11::module)
109
+ set_target_properties(diffvg PROPERTIES PREFIX "${PYTHON_MODULE_PREFIX}"
110
+ SUFFIX "${PYTHON_MODULE_EXTENSION}")
111
+ endif()
112
+
113
+ set_target_properties(diffvg PROPERTIES SKIP_BUILD_RPATH FALSE)
114
+ set_target_properties(diffvg PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE)
115
+ if(UNIX AND NOT APPLE)
116
+ set_target_properties(diffvg PROPERTIES INSTALL_RPATH "$ORIGIN")
117
+ elseif(APPLE)
118
+ set_target_properties(diffvg PROPERTIES INSTALL_RPATH "@loader_path")
119
+ endif()
120
+
121
+ set_property(TARGET diffvg PROPERTY CXX_STANDARD 11)
122
+ set_target_properties(diffvg PROPERTIES PREFIX "")
123
+ # Still enable assertion in release mode
124
+ string( REPLACE "/DNDEBUG" "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}")
125
+ string( REPLACE "-DNDEBUG" "" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}")
126
+ string( REPLACE "/DNDEBUG" "" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
127
+ string( REPLACE "-DNDEBUG" "" CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
128
+ string( REPLACE "/DNDEBUG" "" CMAKE_C_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}")
129
+ string( REPLACE "-DNDEBUG" "" CMAKE_C_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}")
130
+ string( REPLACE "/DNDEBUG" "" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
131
+ string( REPLACE "-DNDEBUG" "" CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
132
+
133
+ if(NOT WIN32)
134
+ find_package(TensorFlow)
135
+ if(TensorFlow_FOUND)
136
+ add_subdirectory(pydiffvg_tensorflow/custom_ops)
137
+ else()
138
+ message(INFO " Building without TensorFlow support (not found)")
139
+ endif()
140
+ endif()
LIVE/LICENSE ADDED
@@ -0,0 +1,661 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ GNU AFFERO GENERAL PUBLIC LICENSE
2
+ Version 3, 19 November 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 Affero General Public License is a free, copyleft license for
11
+ software and other kinds of works, specifically designed to ensure
12
+ cooperation with the community in the case of network server software.
13
+
14
+ The licenses for most software and other practical works are designed
15
+ to take away your freedom to share and change the works. By contrast,
16
+ our General Public Licenses are intended to guarantee your freedom to
17
+ share and change all versions of a program--to make sure it remains free
18
+ software for all its users.
19
+
20
+ When we speak of free software, we are referring to freedom, not
21
+ price. Our General Public Licenses are designed to make sure that you
22
+ have the freedom to distribute copies of free software (and charge for
23
+ them if you wish), that you receive source code or can get it if you
24
+ want it, that you can change the software or use pieces of it in new
25
+ free programs, and that you know you can do these things.
26
+
27
+ Developers that use our General Public Licenses protect your rights
28
+ with two steps: (1) assert copyright on the software, and (2) offer
29
+ you this License which gives you legal permission to copy, distribute
30
+ and/or modify the software.
31
+
32
+ A secondary benefit of defending all users' freedom is that
33
+ improvements made in alternate versions of the program, if they
34
+ receive widespread use, become available for other developers to
35
+ incorporate. Many developers of free software are heartened and
36
+ encouraged by the resulting cooperation. However, in the case of
37
+ software used on network servers, this result may fail to come about.
38
+ The GNU General Public License permits making a modified version and
39
+ letting the public access it on a server without ever releasing its
40
+ source code to the public.
41
+
42
+ The GNU Affero General Public License is designed specifically to
43
+ ensure that, in such cases, the modified source code becomes available
44
+ to the community. It requires the operator of a network server to
45
+ provide the source code of the modified version running there to the
46
+ users of that server. Therefore, public use of a modified version, on
47
+ a publicly accessible server, gives the public access to the source
48
+ code of the modified version.
49
+
50
+ An older license, called the Affero General Public License and
51
+ published by Affero, was designed to accomplish similar goals. This is
52
+ a different license, not a version of the Affero GPL, but Affero has
53
+ released a new version of the Affero GPL which permits relicensing under
54
+ this license.
55
+
56
+ The precise terms and conditions for copying, distribution and
57
+ modification follow.
58
+
59
+ TERMS AND CONDITIONS
60
+
61
+ 0. Definitions.
62
+
63
+ "This License" refers to version 3 of the GNU Affero General Public License.
64
+
65
+ "Copyright" also means copyright-like laws that apply to other kinds of
66
+ works, such as semiconductor masks.
67
+
68
+ "The Program" refers to any copyrightable work licensed under this
69
+ License. Each licensee is addressed as "you". "Licensees" and
70
+ "recipients" may be individuals or organizations.
71
+
72
+ To "modify" a work means to copy from or adapt all or part of the work
73
+ in a fashion requiring copyright permission, other than the making of an
74
+ exact copy. The resulting work is called a "modified version" of the
75
+ earlier work or a work "based on" the earlier work.
76
+
77
+ A "covered work" means either the unmodified Program or a work based
78
+ on the Program.
79
+
80
+ To "propagate" a work means to do anything with it that, without
81
+ permission, would make you directly or secondarily liable for
82
+ infringement under applicable copyright law, except executing it on a
83
+ computer or modifying a private copy. Propagation includes copying,
84
+ distribution (with or without modification), making available to the
85
+ public, and in some countries other activities as well.
86
+
87
+ To "convey" a work means any kind of propagation that enables other
88
+ parties to make or receive copies. Mere interaction with a user through
89
+ a computer network, with no transfer of a copy, is not conveying.
90
+
91
+ An interactive user interface displays "Appropriate Legal Notices"
92
+ to the extent that it includes a convenient and prominently visible
93
+ feature that (1) displays an appropriate copyright notice, and (2)
94
+ tells the user that there is no warranty for the work (except to the
95
+ extent that warranties are provided), that licensees may convey the
96
+ work under this License, and how to view a copy of this License. If
97
+ the interface presents a list of user commands or options, such as a
98
+ menu, a prominent item in the list meets this criterion.
99
+
100
+ 1. Source Code.
101
+
102
+ The "source code" for a work means the preferred form of the work
103
+ for making modifications to it. "Object code" means any non-source
104
+ form of a work.
105
+
106
+ A "Standard Interface" means an interface that either is an official
107
+ standard defined by a recognized standards body, or, in the case of
108
+ interfaces specified for a particular programming language, one that
109
+ is widely used among developers working in that language.
110
+
111
+ The "System Libraries" of an executable work include anything, other
112
+ than the work as a whole, that (a) is included in the normal form of
113
+ packaging a Major Component, but which is not part of that Major
114
+ Component, and (b) serves only to enable use of the work with that
115
+ Major Component, or to implement a Standard Interface for which an
116
+ implementation is available to the public in source code form. A
117
+ "Major Component", in this context, means a major essential component
118
+ (kernel, window system, and so on) of the specific operating system
119
+ (if any) on which the executable work runs, or a compiler used to
120
+ produce the work, or an object code interpreter used to run it.
121
+
122
+ The "Corresponding Source" for a work in object code form means all
123
+ the source code needed to generate, install, and (for an executable
124
+ work) run the object code and to modify the work, including scripts to
125
+ control those activities. However, it does not include the work's
126
+ System Libraries, or general-purpose tools or generally available free
127
+ programs which are used unmodified in performing those activities but
128
+ which are not part of the work. For example, Corresponding Source
129
+ includes interface definition files associated with source files for
130
+ the work, and the source code for shared libraries and dynamically
131
+ linked subprograms that the work is specifically designed to require,
132
+ such as by intimate data communication or control flow between those
133
+ subprograms and other parts of the work.
134
+
135
+ The Corresponding Source need not include anything that users
136
+ can regenerate automatically from other parts of the Corresponding
137
+ Source.
138
+
139
+ The Corresponding Source for a work in source code form is that
140
+ same work.
141
+
142
+ 2. Basic Permissions.
143
+
144
+ All rights granted under this License are granted for the term of
145
+ copyright on the Program, and are irrevocable provided the stated
146
+ conditions are met. This License explicitly affirms your unlimited
147
+ permission to run the unmodified Program. The output from running a
148
+ covered work is covered by this License only if the output, given its
149
+ content, constitutes a covered work. This License acknowledges your
150
+ rights of fair use or other equivalent, as provided by copyright law.
151
+
152
+ You may make, run and propagate covered works that you do not
153
+ convey, without conditions so long as your license otherwise remains
154
+ in force. You may convey covered works to others for the sole purpose
155
+ of having them make modifications exclusively for you, or provide you
156
+ with facilities for running those works, provided that you comply with
157
+ the terms of this License in conveying all material for which you do
158
+ not control copyright. Those thus making or running the covered works
159
+ for you must do so exclusively on your behalf, under your direction
160
+ and control, on terms that prohibit them from making any copies of
161
+ your copyrighted material outside their relationship with you.
162
+
163
+ Conveying under any other circumstances is permitted solely under
164
+ the conditions stated below. Sublicensing is not allowed; section 10
165
+ makes it unnecessary.
166
+
167
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
168
+
169
+ No covered work shall be deemed part of an effective technological
170
+ measure under any applicable law fulfilling obligations under article
171
+ 11 of the WIPO copyright treaty adopted on 20 December 1996, or
172
+ similar laws prohibiting or restricting circumvention of such
173
+ measures.
174
+
175
+ When you convey a covered work, you waive any legal power to forbid
176
+ circumvention of technological measures to the extent such circumvention
177
+ is effected by exercising rights under this License with respect to
178
+ the covered work, and you disclaim any intention to limit operation or
179
+ modification of the work as a means of enforcing, against the work's
180
+ users, your or third parties' legal rights to forbid circumvention of
181
+ technological measures.
182
+
183
+ 4. Conveying Verbatim Copies.
184
+
185
+ You may convey verbatim copies of the Program's source code as you
186
+ receive it, in any medium, provided that you conspicuously and
187
+ appropriately publish on each copy an appropriate copyright notice;
188
+ keep intact all notices stating that this License and any
189
+ non-permissive terms added in accord with section 7 apply to the code;
190
+ keep intact all notices of the absence of any warranty; and give all
191
+ recipients a copy of this License along with the Program.
192
+
193
+ You may charge any price or no price for each copy that you convey,
194
+ and you may offer support or warranty protection for a fee.
195
+
196
+ 5. Conveying Modified Source Versions.
197
+
198
+ You may convey a work based on the Program, or the modifications to
199
+ produce it from the Program, in the form of source code under the
200
+ terms of section 4, provided that you also meet all of these conditions:
201
+
202
+ a) The work must carry prominent notices stating that you modified
203
+ it, and giving a relevant date.
204
+
205
+ b) The work must carry prominent notices stating that it is
206
+ released under this License and any conditions added under section
207
+ 7. This requirement modifies the requirement in section 4 to
208
+ "keep intact all notices".
209
+
210
+ c) You must license the entire work, as a whole, under this
211
+ License to anyone who comes into possession of a copy. This
212
+ License will therefore apply, along with any applicable section 7
213
+ additional terms, to the whole of the work, and all its parts,
214
+ regardless of how they are packaged. This License gives no
215
+ permission to license the work in any other way, but it does not
216
+ invalidate such permission if you have separately received it.
217
+
218
+ d) If the work has interactive user interfaces, each must display
219
+ Appropriate Legal Notices; however, if the Program has interactive
220
+ interfaces that do not display Appropriate Legal Notices, your
221
+ work need not make them do so.
222
+
223
+ A compilation of a covered work with other separate and independent
224
+ works, which are not by their nature extensions of the covered work,
225
+ and which are not combined with it such as to form a larger program,
226
+ in or on a volume of a storage or distribution medium, is called an
227
+ "aggregate" if the compilation and its resulting copyright are not
228
+ used to limit the access or legal rights of the compilation's users
229
+ beyond what the individual works permit. Inclusion of a covered work
230
+ in an aggregate does not cause this License to apply to the other
231
+ parts of the aggregate.
232
+
233
+ 6. Conveying Non-Source Forms.
234
+
235
+ You may convey a covered work in object code form under the terms
236
+ of sections 4 and 5, provided that you also convey the
237
+ machine-readable Corresponding Source under the terms of this License,
238
+ in one of these ways:
239
+
240
+ a) Convey the object code in, or embodied in, a physical product
241
+ (including a physical distribution medium), accompanied by the
242
+ Corresponding Source fixed on a durable physical medium
243
+ customarily used for software interchange.
244
+
245
+ b) Convey the object code in, or embodied in, a physical product
246
+ (including a physical distribution medium), accompanied by a
247
+ written offer, valid for at least three years and valid for as
248
+ long as you offer spare parts or customer support for that product
249
+ model, to give anyone who possesses the object code either (1) a
250
+ copy of the Corresponding Source for all the software in the
251
+ product that is covered by this License, on a durable physical
252
+ medium customarily used for software interchange, for a price no
253
+ more than your reasonable cost of physically performing this
254
+ conveying of source, or (2) access to copy the
255
+ Corresponding Source from a network server at no charge.
256
+
257
+ c) Convey individual copies of the object code with a copy of the
258
+ written offer to provide the Corresponding Source. This
259
+ alternative is allowed only occasionally and noncommercially, and
260
+ only if you received the object code with such an offer, in accord
261
+ with subsection 6b.
262
+
263
+ d) Convey the object code by offering access from a designated
264
+ place (gratis or for a charge), and offer equivalent access to the
265
+ Corresponding Source in the same way through the same place at no
266
+ further charge. You need not require recipients to copy the
267
+ Corresponding Source along with the object code. If the place to
268
+ copy the object code is a network server, the Corresponding Source
269
+ may be on a different server (operated by you or a third party)
270
+ that supports equivalent copying facilities, provided you maintain
271
+ clear directions next to the object code saying where to find the
272
+ Corresponding Source. Regardless of what server hosts the
273
+ Corresponding Source, you remain obligated to ensure that it is
274
+ available for as long as needed to satisfy these requirements.
275
+
276
+ e) Convey the object code using peer-to-peer transmission, provided
277
+ you inform other peers where the object code and Corresponding
278
+ Source of the work are being offered to the general public at no
279
+ charge under subsection 6d.
280
+
281
+ A separable portion of the object code, whose source code is excluded
282
+ from the Corresponding Source as a System Library, need not be
283
+ included in conveying the object code work.
284
+
285
+ A "User Product" is either (1) a "consumer product", which means any
286
+ tangible personal property which is normally used for personal, family,
287
+ or household purposes, or (2) anything designed or sold for incorporation
288
+ into a dwelling. In determining whether a product is a consumer product,
289
+ doubtful cases shall be resolved in favor of coverage. For a particular
290
+ product received by a particular user, "normally used" refers to a
291
+ typical or common use of that class of product, regardless of the status
292
+ of the particular user or of the way in which the particular user
293
+ actually uses, or expects or is expected to use, the product. A product
294
+ is a consumer product regardless of whether the product has substantial
295
+ commercial, industrial or non-consumer uses, unless such uses represent
296
+ the only significant mode of use of the product.
297
+
298
+ "Installation Information" for a User Product means any methods,
299
+ procedures, authorization keys, or other information required to install
300
+ and execute modified versions of a covered work in that User Product from
301
+ a modified version of its Corresponding Source. The information must
302
+ suffice to ensure that the continued functioning of the modified object
303
+ code is in no case prevented or interfered with solely because
304
+ modification has been made.
305
+
306
+ If you convey an object code work under this section in, or with, or
307
+ specifically for use in, a User Product, and the conveying occurs as
308
+ part of a transaction in which the right of possession and use of the
309
+ User Product is transferred to the recipient in perpetuity or for a
310
+ fixed term (regardless of how the transaction is characterized), the
311
+ Corresponding Source conveyed under this section must be accompanied
312
+ by the Installation Information. But this requirement does not apply
313
+ if neither you nor any third party retains the ability to install
314
+ modified object code on the User Product (for example, the work has
315
+ been installed in ROM).
316
+
317
+ The requirement to provide Installation Information does not include a
318
+ requirement to continue to provide support service, warranty, or updates
319
+ for a work that has been modified or installed by the recipient, or for
320
+ the User Product in which it has been modified or installed. Access to a
321
+ network may be denied when the modification itself materially and
322
+ adversely affects the operation of the network or violates the rules and
323
+ protocols for communication across the network.
324
+
325
+ Corresponding Source conveyed, and Installation Information provided,
326
+ in accord with this section must be in a format that is publicly
327
+ documented (and with an implementation available to the public in
328
+ source code form), and must require no special password or key for
329
+ unpacking, reading or copying.
330
+
331
+ 7. Additional Terms.
332
+
333
+ "Additional permissions" are terms that supplement the terms of this
334
+ License by making exceptions from one or more of its conditions.
335
+ Additional permissions that are applicable to the entire Program shall
336
+ be treated as though they were included in this License, to the extent
337
+ that they are valid under applicable law. If additional permissions
338
+ apply only to part of the Program, that part may be used separately
339
+ under those permissions, but the entire Program remains governed by
340
+ this License without regard to the additional permissions.
341
+
342
+ When you convey a copy of a covered work, you may at your option
343
+ remove any additional permissions from that copy, or from any part of
344
+ it. (Additional permissions may be written to require their own
345
+ removal in certain cases when you modify the work.) You may place
346
+ additional permissions on material, added by you to a covered work,
347
+ for which you have or can give appropriate copyright permission.
348
+
349
+ Notwithstanding any other provision of this License, for material you
350
+ add to a covered work, you may (if authorized by the copyright holders of
351
+ that material) supplement the terms of this License with terms:
352
+
353
+ a) Disclaiming warranty or limiting liability differently from the
354
+ terms of sections 15 and 16 of this License; or
355
+
356
+ b) Requiring preservation of specified reasonable legal notices or
357
+ author attributions in that material or in the Appropriate Legal
358
+ Notices displayed by works containing it; or
359
+
360
+ c) Prohibiting misrepresentation of the origin of that material, or
361
+ requiring that modified versions of such material be marked in
362
+ reasonable ways as different from the original version; or
363
+
364
+ d) Limiting the use for publicity purposes of names of licensors or
365
+ authors of the material; or
366
+
367
+ e) Declining to grant rights under trademark law for use of some
368
+ trade names, trademarks, or service marks; or
369
+
370
+ f) Requiring indemnification of licensors and authors of that
371
+ material by anyone who conveys the material (or modified versions of
372
+ it) with contractual assumptions of liability to the recipient, for
373
+ any liability that these contractual assumptions directly impose on
374
+ those licensors and authors.
375
+
376
+ All other non-permissive additional terms are considered "further
377
+ restrictions" within the meaning of section 10. If the Program as you
378
+ received it, or any part of it, contains a notice stating that it is
379
+ governed by this License along with a term that is a further
380
+ restriction, you may remove that term. If a license document contains
381
+ a further restriction but permits relicensing or conveying under this
382
+ License, you may add to a covered work material governed by the terms
383
+ of that license document, provided that the further restriction does
384
+ not survive such relicensing or conveying.
385
+
386
+ If you add terms to a covered work in accord with this section, you
387
+ must place, in the relevant source files, a statement of the
388
+ additional terms that apply to those files, or a notice indicating
389
+ where to find the applicable terms.
390
+
391
+ Additional terms, permissive or non-permissive, may be stated in the
392
+ form of a separately written license, or stated as exceptions;
393
+ the above requirements apply either way.
394
+
395
+ 8. Termination.
396
+
397
+ You may not propagate or modify a covered work except as expressly
398
+ provided under this License. Any attempt otherwise to propagate or
399
+ modify it is void, and will automatically terminate your rights under
400
+ this License (including any patent licenses granted under the third
401
+ paragraph of section 11).
402
+
403
+ However, if you cease all violation of this License, then your
404
+ license from a particular copyright holder is reinstated (a)
405
+ provisionally, unless and until the copyright holder explicitly and
406
+ finally terminates your license, and (b) permanently, if the copyright
407
+ holder fails to notify you of the violation by some reasonable means
408
+ prior to 60 days after the cessation.
409
+
410
+ Moreover, your license from a particular copyright holder is
411
+ reinstated permanently if the copyright holder notifies you of the
412
+ violation by some reasonable means, this is the first time you have
413
+ received notice of violation of this License (for any work) from that
414
+ copyright holder, and you cure the violation prior to 30 days after
415
+ your receipt of the notice.
416
+
417
+ Termination of your rights under this section does not terminate the
418
+ licenses of parties who have received copies or rights from you under
419
+ this License. If your rights have been terminated and not permanently
420
+ reinstated, you do not qualify to receive new licenses for the same
421
+ material under section 10.
422
+
423
+ 9. Acceptance Not Required for Having Copies.
424
+
425
+ You are not required to accept this License in order to receive or
426
+ run a copy of the Program. Ancillary propagation of a covered work
427
+ occurring solely as a consequence of using peer-to-peer transmission
428
+ to receive a copy likewise does not require acceptance. However,
429
+ nothing other than this License grants you permission to propagate or
430
+ modify any covered work. These actions infringe copyright if you do
431
+ not accept this License. Therefore, by modifying or propagating a
432
+ covered work, you indicate your acceptance of this License to do so.
433
+
434
+ 10. Automatic Licensing of Downstream Recipients.
435
+
436
+ Each time you convey a covered work, the recipient automatically
437
+ receives a license from the original licensors, to run, modify and
438
+ propagate that work, subject to this License. You are not responsible
439
+ for enforcing compliance by third parties with this License.
440
+
441
+ An "entity transaction" is a transaction transferring control of an
442
+ organization, or substantially all assets of one, or subdividing an
443
+ organization, or merging organizations. If propagation of a covered
444
+ work results from an entity transaction, each party to that
445
+ transaction who receives a copy of the work also receives whatever
446
+ licenses to the work the party's predecessor in interest had or could
447
+ give under the previous paragraph, plus a right to possession of the
448
+ Corresponding Source of the work from the predecessor in interest, if
449
+ the predecessor has it or can get it with reasonable efforts.
450
+
451
+ You may not impose any further restrictions on the exercise of the
452
+ rights granted or affirmed under this License. For example, you may
453
+ not impose a license fee, royalty, or other charge for exercise of
454
+ rights granted under this License, and you may not initiate litigation
455
+ (including a cross-claim or counterclaim in a lawsuit) alleging that
456
+ any patent claim is infringed by making, using, selling, offering for
457
+ sale, or importing the Program or any portion of it.
458
+
459
+ 11. Patents.
460
+
461
+ A "contributor" is a copyright holder who authorizes use under this
462
+ License of the Program or a work on which the Program is based. The
463
+ work thus licensed is called the contributor's "contributor version".
464
+
465
+ A contributor's "essential patent claims" are all patent claims
466
+ owned or controlled by the contributor, whether already acquired or
467
+ hereafter acquired, that would be infringed by some manner, permitted
468
+ by this License, of making, using, or selling its contributor version,
469
+ but do not include claims that would be infringed only as a
470
+ consequence of further modification of the contributor version. For
471
+ purposes of this definition, "control" includes the right to grant
472
+ patent sublicenses in a manner consistent with the requirements of
473
+ this License.
474
+
475
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
476
+ patent license under the contributor's essential patent claims, to
477
+ make, use, sell, offer for sale, import and otherwise run, modify and
478
+ propagate the contents of its contributor version.
479
+
480
+ In the following three paragraphs, a "patent license" is any express
481
+ agreement or commitment, however denominated, not to enforce a patent
482
+ (such as an express permission to practice a patent or covenant not to
483
+ sue for patent infringement). To "grant" such a patent license to a
484
+ party means to make such an agreement or commitment not to enforce a
485
+ patent against the party.
486
+
487
+ If you convey a covered work, knowingly relying on a patent license,
488
+ and the Corresponding Source of the work is not available for anyone
489
+ to copy, free of charge and under the terms of this License, through a
490
+ publicly available network server or other readily accessible means,
491
+ then you must either (1) cause the Corresponding Source to be so
492
+ available, or (2) arrange to deprive yourself of the benefit of the
493
+ patent license for this particular work, or (3) arrange, in a manner
494
+ consistent with the requirements of this License, to extend the patent
495
+ license to downstream recipients. "Knowingly relying" means you have
496
+ actual knowledge that, but for the patent license, your conveying the
497
+ covered work in a country, or your recipient's use of the covered work
498
+ in a country, would infringe one or more identifiable patents in that
499
+ country that you have reason to believe are valid.
500
+
501
+ If, pursuant to or in connection with a single transaction or
502
+ arrangement, you convey, or propagate by procuring conveyance of, a
503
+ covered work, and grant a patent license to some of the parties
504
+ receiving the covered work authorizing them to use, propagate, modify
505
+ or convey a specific copy of the covered work, then the patent license
506
+ you grant is automatically extended to all recipients of the covered
507
+ work and works based on it.
508
+
509
+ A patent license is "discriminatory" if it does not include within
510
+ the scope of its coverage, prohibits the exercise of, or is
511
+ conditioned on the non-exercise of one or more of the rights that are
512
+ specifically granted under this License. You may not convey a covered
513
+ work if you are a party to an arrangement with a third party that is
514
+ in the business of distributing software, under which you make payment
515
+ to the third party based on the extent of your activity of conveying
516
+ the work, and under which the third party grants, to any of the
517
+ parties who would receive the covered work from you, a discriminatory
518
+ patent license (a) in connection with copies of the covered work
519
+ conveyed by you (or copies made from those copies), or (b) primarily
520
+ for and in connection with specific products or compilations that
521
+ contain the covered work, unless you entered into that arrangement,
522
+ or that patent license was granted, prior to 28 March 2007.
523
+
524
+ Nothing in this License shall be construed as excluding or limiting
525
+ any implied license or other defenses to infringement that may
526
+ otherwise be available to you under applicable patent law.
527
+
528
+ 12. No Surrender of Others' Freedom.
529
+
530
+ If conditions are imposed on you (whether by court order, agreement or
531
+ otherwise) that contradict the conditions of this License, they do not
532
+ excuse you from the conditions of this License. If you cannot convey a
533
+ covered work so as to satisfy simultaneously your obligations under this
534
+ License and any other pertinent obligations, then as a consequence you may
535
+ not convey it at all. For example, if you agree to terms that obligate you
536
+ to collect a royalty for further conveying from those to whom you convey
537
+ the Program, the only way you could satisfy both those terms and this
538
+ License would be to refrain entirely from conveying the Program.
539
+
540
+ 13. Remote Network Interaction; Use with the GNU General Public License.
541
+
542
+ Notwithstanding any other provision of this License, if you modify the
543
+ Program, your modified version must prominently offer all users
544
+ interacting with it remotely through a computer network (if your version
545
+ supports such interaction) an opportunity to receive the Corresponding
546
+ Source of your version by providing access to the Corresponding Source
547
+ from a network server at no charge, through some standard or customary
548
+ means of facilitating copying of software. This Corresponding Source
549
+ shall include the Corresponding Source for any work covered by version 3
550
+ of the GNU General Public License that is incorporated pursuant to the
551
+ following paragraph.
552
+
553
+ Notwithstanding any other provision of this License, you have
554
+ permission to link or combine any covered work with a work licensed
555
+ under version 3 of the GNU General Public License into a single
556
+ combined work, and to convey the resulting work. The terms of this
557
+ License will continue to apply to the part which is the covered work,
558
+ but the work with which it is combined will remain governed by version
559
+ 3 of the GNU General Public License.
560
+
561
+ 14. Revised Versions of this License.
562
+
563
+ The Free Software Foundation may publish revised and/or new versions of
564
+ the GNU Affero General Public License from time to time. Such new versions
565
+ will be similar in spirit to the present version, but may differ in detail to
566
+ address new problems or concerns.
567
+
568
+ Each version is given a distinguishing version number. If the
569
+ Program specifies that a certain numbered version of the GNU Affero General
570
+ Public License "or any later version" applies to it, you have the
571
+ option of following the terms and conditions either of that numbered
572
+ version or of any later version published by the Free Software
573
+ Foundation. If the Program does not specify a version number of the
574
+ GNU Affero General Public License, you may choose any version ever published
575
+ by the Free Software Foundation.
576
+
577
+ If the Program specifies that a proxy can decide which future
578
+ versions of the GNU Affero General Public License can be used, that proxy's
579
+ public statement of acceptance of a version permanently authorizes you
580
+ to choose that version for the Program.
581
+
582
+ Later license versions may give you additional or different
583
+ permissions. However, no additional obligations are imposed on any
584
+ author or copyright holder as a result of your choosing to follow a
585
+ later version.
586
+
587
+ 15. Disclaimer of Warranty.
588
+
589
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
590
+ APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
591
+ HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
592
+ OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
593
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
594
+ PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
595
+ IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
596
+ ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
597
+
598
+ 16. Limitation of Liability.
599
+
600
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
601
+ WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
602
+ THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
603
+ GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
604
+ USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
605
+ DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
606
+ PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
607
+ EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
608
+ SUCH DAMAGES.
609
+
610
+ 17. Interpretation of Sections 15 and 16.
611
+
612
+ If the disclaimer of warranty and limitation of liability provided
613
+ above cannot be given local legal effect according to their terms,
614
+ reviewing courts shall apply local law that most closely approximates
615
+ an absolute waiver of all civil liability in connection with the
616
+ Program, unless a warranty or assumption of liability accompanies a
617
+ copy of the Program in return for a fee.
618
+
619
+ END OF TERMS AND CONDITIONS
620
+
621
+ How to Apply These Terms to Your New Programs
622
+
623
+ If you develop a new program, and you want it to be of the greatest
624
+ possible use to the public, the best way to achieve this is to make it
625
+ free software which everyone can redistribute and change under these terms.
626
+
627
+ To do so, attach the following notices to the program. It is safest
628
+ to attach them to the start of each source file to most effectively
629
+ state the exclusion of warranty; and each file should have at least
630
+ the "copyright" line and a pointer to where the full notice is found.
631
+
632
+ <one line to give the program's name and a brief idea of what it does.>
633
+ Copyright (C) <year> <name of author>
634
+
635
+ This program is free software: you can redistribute it and/or modify
636
+ it under the terms of the GNU Affero General Public License as published
637
+ by the Free Software Foundation, either version 3 of the License, or
638
+ (at your option) any later version.
639
+
640
+ This program is distributed in the hope that it will be useful,
641
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
642
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
643
+ GNU Affero General Public License for more details.
644
+
645
+ You should have received a copy of the GNU Affero General Public License
646
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
647
+
648
+ Also add information on how to contact you by electronic and paper mail.
649
+
650
+ If your software can interact with users remotely through a computer
651
+ network, you should also make sure that it provides a way for users to
652
+ get its source. For example, if your program is a web application, its
653
+ interface could display a "Source" link that leads users to an archive
654
+ of the code. There are many ways you could offer source, and different
655
+ solutions will be better for different programs; see section 13 for the
656
+ specific requirements.
657
+
658
+ You should also get your employer (if you work as a programmer) or school,
659
+ if any, to sign a "copyright disclaimer" for the program, if necessary.
660
+ For more information on this, and how to apply and follow the GNU AGPL, see
661
+ <https://www.gnu.org/licenses/>.
LIVE/README.md ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # LIVE-pytorch
2
+ Towards Layer-wise Image Vectorization
3
+
4
+ ### Updated for rebuttal (Jan/28/2022):
5
+ #### User study
6
+ We create a [user study](https://wj.qq.com/s2/9665341/19ed) as suggested. A more complex user study will be added in the revised version.
7
+
8
+ The results are collected here: [user study details](user_study_state.csv)
9
+
10
+ #### Code installation
11
+
12
+ we added detailed [conda env file](env.yml) and collected detail [system information](system_info.txt) to help the installation.
13
+
14
+ A more detailed docker and Google Colab demo will be provided.
15
+
16
+
17
+ <div align="center">
18
+ <img src="example.png" width="650px" height="300px">
19
+ </div>
20
+ LIVE is able to explicitly presents a Layer-wise representation for simple images.
21
+
22
+ ## Installation
23
+ ```bash
24
+ pip3 install torch torchvision
25
+ pip install svgwrite
26
+ pip install svgpathtools
27
+ pip install cssutils
28
+ pip install numba
29
+ pip install torch-tools
30
+ pip install visdom
31
+ pip install scikit-fmm
32
+ pip install opencv-python==4.5.4.60
33
+ pip install easydict
34
+ pip install scikit-fmm
35
+
36
+ ```
37
+ Next, please refer DiffVG to install [pydiffvg](https://github.com/BachiLi/diffvg)
38
+
39
+
40
+ ## Run
41
+ ```bash
42
+ python main.py --config config/all.yaml --experiment experiment_8x1 --signature demo1 --target data/demo1.png
43
+ ```
44
+ Please modify the config files to change configurations.
LIVE/colab.py ADDED
@@ -0,0 +1,687 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Here are some use cases:
3
+ python main.py --config config/all.yaml --experiment experiment_8x1 --signature demo1 --target data/demo1.png
4
+ """
5
+ import pydiffvg
6
+ import torch
7
+ import cv2
8
+ import matplotlib.pyplot as plt
9
+ import random
10
+ import argparse
11
+ import math
12
+ import errno
13
+ from tqdm import tqdm
14
+ from torch.optim.lr_scheduler import CosineAnnealingLR, LambdaLR
15
+ from torch.nn.functional import adaptive_avg_pool2d
16
+ import warnings
17
+ warnings.filterwarnings("ignore")
18
+
19
+ import PIL
20
+ import PIL.Image
21
+ import os
22
+ import os.path as osp
23
+ import numpy as np
24
+ import numpy.random as npr
25
+ import shutil
26
+ import copy
27
+ # import skfmm
28
+ from xing_loss import xing_loss
29
+
30
+ import yaml
31
+ from easydict import EasyDict as edict
32
+
33
+
34
+ pydiffvg.set_print_timing(False)
35
+ gamma = 1.0
36
+
37
+ ##########
38
+ # helper #
39
+ ##########
40
+
41
+ from utils import \
42
+ get_experiment_id, \
43
+ get_path_schedule, \
44
+ edict_2_dict, \
45
+ check_and_create_dir
46
+
47
+ def get_bezier_circle(radius=1, segments=4, bias=None):
48
+ points = []
49
+ if bias is None:
50
+ bias = (random.random(), random.random())
51
+ avg_degree = 360 / (segments*3)
52
+ for i in range(0, segments*3):
53
+ point = (np.cos(np.deg2rad(i * avg_degree)),
54
+ np.sin(np.deg2rad(i * avg_degree)))
55
+ points.append(point)
56
+ points = torch.tensor(points)
57
+ points = (points)*radius + torch.tensor(bias).unsqueeze(dim=0)
58
+ points = points.type(torch.FloatTensor)
59
+ return points
60
+
61
+ def get_sdf(phi, method='skfmm', **kwargs):
62
+ if method == 'skfmm':
63
+ import skfmm
64
+ phi = (phi-0.5)*2
65
+ if (phi.max() <= 0) or (phi.min() >= 0):
66
+ return np.zeros(phi.shape).astype(np.float32)
67
+ sd = skfmm.distance(phi, dx=1)
68
+
69
+ flip_negative = kwargs.get('flip_negative', True)
70
+ if flip_negative:
71
+ sd = np.abs(sd)
72
+
73
+ truncate = kwargs.get('truncate', 10)
74
+ sd = np.clip(sd, -truncate, truncate)
75
+ # print(f"max sd value is: {sd.max()}")
76
+
77
+ zero2max = kwargs.get('zero2max', True)
78
+ if zero2max and flip_negative:
79
+ sd = sd.max() - sd
80
+ elif zero2max:
81
+ raise ValueError
82
+
83
+ normalize = kwargs.get('normalize', 'sum')
84
+ if normalize == 'sum':
85
+ sd /= sd.sum()
86
+ elif normalize == 'to1':
87
+ sd /= sd.max()
88
+ return sd
89
+
90
+ def parse_args():
91
+ parser = argparse.ArgumentParser()
92
+ parser.add_argument('--debug', action='store_true', default=False)
93
+ parser.add_argument("--config", type=str)
94
+ parser.add_argument("--experiment", type=str)
95
+ parser.add_argument("--seed", type=int)
96
+ parser.add_argument("--target", type=str, help="target image path")
97
+ parser.add_argument('--log_dir', metavar='DIR', default="log/debug")
98
+ parser.add_argument('--initial', type=str, default="random", choices=['random', 'circle'])
99
+ parser.add_argument('--signature', nargs='+', type=str)
100
+ parser.add_argument('--seginit', nargs='+', type=str)
101
+ parser.add_argument("--num_segments", type=int, default=4)
102
+ # parser.add_argument("--num_paths", type=str, default="1,1,1")
103
+ # parser.add_argument("--num_iter", type=int, default=500)
104
+ # parser.add_argument('--free', action='store_true')
105
+ # Please ensure that image resolution is divisible by pool_size; otherwise the performance would drop a lot.
106
+ # parser.add_argument('--pool_size', type=int, default=40, help="the pooled image size for next path initialization")
107
+ # parser.add_argument('--save_loss', action='store_true')
108
+ # parser.add_argument('--save_init', action='store_true')
109
+ # parser.add_argument('--save_image', action='store_true')
110
+ # parser.add_argument('--save_video', action='store_true')
111
+ # parser.add_argument('--print_weight', action='store_true')
112
+ # parser.add_argument('--circle_init_radius', type=float)
113
+ cfg = edict()
114
+ args = parser.parse_args()
115
+ cfg.debug = args.debug
116
+ cfg.config = args.config
117
+ cfg.experiment = args.experiment
118
+ cfg.seed = args.seed
119
+ cfg.target = args.target
120
+ cfg.log_dir = args.log_dir
121
+ cfg.initial = args.initial
122
+ cfg.signature = args.signature
123
+ # set cfg num_segments in command
124
+ cfg.num_segments = args.num_segments
125
+ if args.seginit is not None:
126
+ cfg.seginit = edict()
127
+ cfg.seginit.type = args.seginit[0]
128
+ if cfg.seginit.type == 'circle':
129
+ cfg.seginit.radius = float(args.seginit[1])
130
+ return cfg
131
+
132
+ def ycrcb_conversion(im, format='[bs x 3 x 2D]', reverse=False):
133
+ mat = torch.FloatTensor([
134
+ [ 65.481/255, 128.553/255, 24.966/255], # ranged_from [0, 219/255]
135
+ [-37.797/255, -74.203/255, 112.000/255], # ranged_from [-112/255, 112/255]
136
+ [112.000/255, -93.786/255, -18.214/255], # ranged_from [-112/255, 112/255]
137
+ ]).to(im.device)
138
+
139
+ if reverse:
140
+ mat = mat.inverse()
141
+
142
+ if format == '[bs x 3 x 2D]':
143
+ im = im.permute(0, 2, 3, 1)
144
+ im = torch.matmul(im, mat.T)
145
+ im = im.permute(0, 3, 1, 2).contiguous()
146
+ return im
147
+ elif format == '[2D x 3]':
148
+ im = torch.matmul(im, mat.T)
149
+ return im
150
+ else:
151
+ raise ValueError
152
+
153
+ class random_coord_init():
154
+ def __init__(self, canvas_size):
155
+ self.canvas_size = canvas_size
156
+ def __call__(self):
157
+ h, w = self.canvas_size
158
+ return [npr.uniform(0, 1)*w, npr.uniform(0, 1)*h]
159
+
160
+ class naive_coord_init():
161
+ def __init__(self, pred, gt, format='[bs x c x 2D]', replace_sampling=True):
162
+ if isinstance(pred, torch.Tensor):
163
+ pred = pred.detach().cpu().numpy()
164
+ if isinstance(gt, torch.Tensor):
165
+ gt = gt.detach().cpu().numpy()
166
+
167
+ if format == '[bs x c x 2D]':
168
+ self.map = ((pred[0] - gt[0])**2).sum(0)
169
+ elif format == ['[2D x c]']:
170
+ self.map = ((pred - gt)**2).sum(-1)
171
+ else:
172
+ raise ValueError
173
+ self.replace_sampling = replace_sampling
174
+
175
+ def __call__(self):
176
+ coord = np.where(self.map == self.map.max())
177
+ coord_h, coord_w = coord[0][0], coord[1][0]
178
+ if self.replace_sampling:
179
+ self.map[coord_h, coord_w] = -1
180
+ return [coord_w, coord_h]
181
+
182
+
183
+ class sparse_coord_init():
184
+ def __init__(self, pred, gt, format='[bs x c x 2D]', quantile_interval=200, nodiff_thres=0.1):
185
+ if isinstance(pred, torch.Tensor):
186
+ pred = pred.detach().cpu().numpy()
187
+ if isinstance(gt, torch.Tensor):
188
+ gt = gt.detach().cpu().numpy()
189
+ if format == '[bs x c x 2D]':
190
+ self.map = ((pred[0] - gt[0])**2).sum(0)
191
+ self.reference_gt = copy.deepcopy(
192
+ np.transpose(gt[0], (1, 2, 0)))
193
+ elif format == ['[2D x c]']:
194
+ self.map = (np.abs(pred - gt)).sum(-1)
195
+ self.reference_gt = copy.deepcopy(gt[0])
196
+ else:
197
+ raise ValueError
198
+ # OptionA: Zero too small errors to avoid the error too small deadloop
199
+ self.map[self.map < nodiff_thres] = 0
200
+ quantile_interval = np.linspace(0., 1., quantile_interval)
201
+ quantized_interval = np.quantile(self.map, quantile_interval)
202
+ # remove redundant
203
+ quantized_interval = np.unique(quantized_interval)
204
+ quantized_interval = sorted(quantized_interval[1:-1])
205
+ self.map = np.digitize(self.map, quantized_interval, right=False)
206
+ self.map = np.clip(self.map, 0, 255).astype(np.uint8)
207
+ self.idcnt = {}
208
+ for idi in sorted(np.unique(self.map)):
209
+ self.idcnt[idi] = (self.map==idi).sum()
210
+ self.idcnt.pop(min(self.idcnt.keys()))
211
+ # remove smallest one to remove the correct region
212
+ def __call__(self):
213
+ if len(self.idcnt) == 0:
214
+ h, w = self.map.shape
215
+ return [npr.uniform(0, 1)*w, npr.uniform(0, 1)*h]
216
+ target_id = max(self.idcnt, key=self.idcnt.get)
217
+ _, component, cstats, ccenter = cv2.connectedComponentsWithStats(
218
+ (self.map==target_id).astype(np.uint8), connectivity=4)
219
+ # remove cid = 0, it is the invalid area
220
+ csize = [ci[-1] for ci in cstats[1:]]
221
+ target_cid = csize.index(max(csize))+1
222
+ center = ccenter[target_cid][::-1]
223
+ coord = np.stack(np.where(component == target_cid)).T
224
+ dist = np.linalg.norm(coord-center, axis=1)
225
+ target_coord_id = np.argmin(dist)
226
+ coord_h, coord_w = coord[target_coord_id]
227
+ # replace_sampling
228
+ self.idcnt[target_id] -= max(csize)
229
+ if self.idcnt[target_id] == 0:
230
+ self.idcnt.pop(target_id)
231
+ self.map[component == target_cid] = 0
232
+ return [coord_w, coord_h]
233
+
234
+
235
+ def init_shapes(num_paths,
236
+ num_segments,
237
+ canvas_size,
238
+ seginit_cfg,
239
+ shape_cnt,
240
+ pos_init_method=None,
241
+ trainable_stroke=False,
242
+ **kwargs):
243
+ shapes = []
244
+ shape_groups = []
245
+ h, w = canvas_size
246
+
247
+ # change path init location
248
+ if pos_init_method is None:
249
+ pos_init_method = random_coord_init(canvas_size=canvas_size)
250
+
251
+ for i in range(num_paths):
252
+ num_control_points = [2] * num_segments
253
+
254
+ if seginit_cfg.type=="random":
255
+ points = []
256
+ p0 = pos_init_method()
257
+ color_ref = copy.deepcopy(p0)
258
+ points.append(p0)
259
+ for j in range(num_segments):
260
+ radius = seginit_cfg.radius
261
+ p1 = (p0[0] + radius * npr.uniform(-0.5, 0.5),
262
+ p0[1] + radius * npr.uniform(-0.5, 0.5))
263
+ p2 = (p1[0] + radius * npr.uniform(-0.5, 0.5),
264
+ p1[1] + radius * npr.uniform(-0.5, 0.5))
265
+ p3 = (p2[0] + radius * npr.uniform(-0.5, 0.5),
266
+ p2[1] + radius * npr.uniform(-0.5, 0.5))
267
+ points.append(p1)
268
+ points.append(p2)
269
+ if j < num_segments - 1:
270
+ points.append(p3)
271
+ p0 = p3
272
+ points = torch.FloatTensor(points)
273
+
274
+ # circle points initialization
275
+ elif seginit_cfg.type=="circle":
276
+ radius = seginit_cfg.radius
277
+ if radius is None:
278
+ radius = npr.uniform(0.5, 1)
279
+ center = pos_init_method()
280
+ color_ref = copy.deepcopy(center)
281
+ points = get_bezier_circle(
282
+ radius=radius, segments=num_segments,
283
+ bias=center)
284
+
285
+ path = pydiffvg.Path(num_control_points = torch.LongTensor(num_control_points),
286
+ points = points,
287
+ stroke_width = torch.tensor(0.0),
288
+ is_closed = True)
289
+ shapes.append(path)
290
+ # !!!!!!problem is here. the shape group shape_ids is wrong
291
+
292
+ if 'gt' in kwargs:
293
+ wref, href = color_ref
294
+ wref = max(0, min(int(wref), w-1))
295
+ href = max(0, min(int(href), h-1))
296
+ fill_color_init = list(gt[0, :, href, wref]) + [1.]
297
+ fill_color_init = torch.FloatTensor(fill_color_init)
298
+ stroke_color_init = torch.FloatTensor(npr.uniform(size=[4]))
299
+ else:
300
+ fill_color_init = torch.FloatTensor(npr.uniform(size=[4]))
301
+ stroke_color_init = torch.FloatTensor(npr.uniform(size=[4]))
302
+
303
+ path_group = pydiffvg.ShapeGroup(
304
+ shape_ids = torch.LongTensor([shape_cnt+i]),
305
+ fill_color = fill_color_init,
306
+ stroke_color = stroke_color_init,
307
+ )
308
+ shape_groups.append(path_group)
309
+
310
+ point_var = []
311
+ color_var = []
312
+
313
+ for path in shapes:
314
+ path.points.requires_grad = True
315
+ point_var.append(path.points)
316
+ for group in shape_groups:
317
+ group.fill_color.requires_grad = True
318
+ color_var.append(group.fill_color)
319
+
320
+ if trainable_stroke:
321
+ stroke_width_var = []
322
+ stroke_color_var = []
323
+ for path in shapes:
324
+ path.stroke_width.requires_grad = True
325
+ stroke_width_var.append(path.stroke_width)
326
+ for group in shape_groups:
327
+ group.stroke_color.requires_grad = True
328
+ stroke_color_var.append(group.stroke_color)
329
+ return shapes, shape_groups, point_var, color_var, stroke_width_var, stroke_color_var
330
+ else:
331
+ return shapes, shape_groups, point_var, color_var
332
+
333
+ class linear_decay_lrlambda_f(object):
334
+ def __init__(self, decay_every, decay_ratio):
335
+ self.decay_every = decay_every
336
+ self.decay_ratio = decay_ratio
337
+
338
+ def __call__(self, n):
339
+ decay_time = n//self.decay_every
340
+ decay_step = n %self.decay_every
341
+ lr_s = self.decay_ratio**decay_time
342
+ lr_e = self.decay_ratio**(decay_time+1)
343
+ r = decay_step/self.decay_every
344
+ lr = lr_s * (1-r) + lr_e * r
345
+ return lr
346
+
347
+
348
+ if __name__ == "__main__":
349
+
350
+ ###############
351
+ # make config #
352
+ ###############
353
+
354
+ cfg_arg = parse_args()
355
+ with open(cfg_arg.config, 'r') as f:
356
+ cfg = yaml.load(f, Loader=yaml.FullLoader)
357
+ cfg_default = edict(cfg['default'])
358
+ cfg = edict(cfg[cfg_arg.experiment])
359
+ cfg.update(cfg_default)
360
+ cfg.update(cfg_arg)
361
+ cfg.exid = get_experiment_id(cfg.debug)
362
+
363
+ cfg.experiment_dir = \
364
+ osp.join(cfg.log_dir, '{}_{}'.format(cfg.exid, '_'.join(cfg.signature)))
365
+ configfile = osp.join(cfg.experiment_dir, 'config.yaml')
366
+ check_and_create_dir(configfile)
367
+ with open(osp.join(configfile), 'w') as f:
368
+ yaml.dump(edict_2_dict(cfg), f)
369
+
370
+ # Use GPU if available
371
+ pydiffvg.set_use_gpu(torch.cuda.is_available())
372
+ device = pydiffvg.get_device()
373
+
374
+ gt = np.array(PIL.Image.open(cfg.target))
375
+ print(f"Input image shape is: {gt.shape}")
376
+ if len(gt.shape) == 2:
377
+ print("Converting the gray-scale image to RGB.")
378
+ gt = gt.unsqueeze(dim=-1).repeat(1,1,3)
379
+ if gt.shape[2] == 4:
380
+ print("Input image includes alpha channel, simply dropout alpha channel.")
381
+ gt = gt[:, :, :3]
382
+ gt = (gt/255).astype(np.float32)
383
+ gt = torch.FloatTensor(gt).permute(2, 0, 1)[None].to(device)
384
+ if cfg.use_ycrcb:
385
+ gt = ycrcb_conversion(gt)
386
+ h, w = gt.shape[2:]
387
+
388
+ path_schedule = get_path_schedule(**cfg.path_schedule)
389
+
390
+ if cfg.seed is not None:
391
+ random.seed(cfg.seed)
392
+ npr.seed(cfg.seed)
393
+ torch.manual_seed(cfg.seed)
394
+ render = pydiffvg.RenderFunction.apply
395
+
396
+ shapes_record, shape_groups_record = [], []
397
+
398
+ region_loss = None
399
+ loss_matrix = []
400
+
401
+ para_point, para_color = {}, {}
402
+ if cfg.trainable.stroke:
403
+ para_stroke_width, para_stroke_color = {}, {}
404
+
405
+ pathn_record = []
406
+ # Background
407
+ if cfg.trainable.bg:
408
+ # meancolor = gt.mean([2, 3])[0]
409
+ para_bg = torch.tensor([1., 1., 1.], requires_grad=True, device=device)
410
+ else:
411
+ if cfg.use_ycrcb:
412
+ para_bg = torch.tensor([219/255, 0, 0], requires_grad=False, device=device)
413
+ else:
414
+ para_bg = torch.tensor([1., 1., 1.], requires_grad=False, device=device)
415
+
416
+ ##################
417
+ # start_training #
418
+ ##################
419
+
420
+ loss_weight = None
421
+ loss_weight_keep = 0
422
+ if cfg.coord_init.type == 'naive':
423
+ pos_init_method = naive_coord_init(
424
+ para_bg.view(1, -1, 1, 1).repeat(1, 1, h, w), gt)
425
+ elif cfg.coord_init.type == 'sparse':
426
+ pos_init_method = sparse_coord_init(
427
+ para_bg.view(1, -1, 1, 1).repeat(1, 1, h, w), gt)
428
+ elif cfg.coord_init.type == 'random':
429
+ pos_init_method = random_coord_init([h, w])
430
+ else:
431
+ raise ValueError
432
+
433
+ lrlambda_f = linear_decay_lrlambda_f(cfg.num_iter, 0.4)
434
+ optim_schedular_dict = {}
435
+
436
+ for path_idx, pathn in enumerate(path_schedule):
437
+ loss_list = []
438
+ print("=> Adding [{}] paths, [{}] ...".format(pathn, cfg.seginit.type))
439
+ pathn_record.append(pathn)
440
+ pathn_record_str = '-'.join([str(i) for i in pathn_record])
441
+
442
+ # initialize new shapes related stuffs.
443
+ if cfg.trainable.stroke:
444
+ shapes, shape_groups, point_var, color_var, stroke_width_var, stroke_color_var = init_shapes(
445
+ pathn, cfg.num_segments, (h, w),
446
+ cfg.seginit, len(shapes_record),
447
+ pos_init_method,
448
+ trainable_stroke=True,
449
+ gt=gt, )
450
+ para_stroke_width[path_idx] = stroke_width_var
451
+ para_stroke_color[path_idx] = stroke_color_var
452
+ else:
453
+ shapes, shape_groups, point_var, color_var = init_shapes(
454
+ pathn, cfg.num_segments, (h, w),
455
+ cfg.seginit, len(shapes_record),
456
+ pos_init_method,
457
+ trainable_stroke=False,
458
+ gt=gt, )
459
+
460
+ shapes_record += shapes
461
+ shape_groups_record += shape_groups
462
+
463
+ if cfg.save.init:
464
+ filename = os.path.join(
465
+ cfg.experiment_dir, "svg-init",
466
+ "{}-init.svg".format(pathn_record_str))
467
+ check_and_create_dir(filename)
468
+ pydiffvg.save_svg(
469
+ filename, w, h,
470
+ shapes_record, shape_groups_record)
471
+
472
+ para = {}
473
+ if (cfg.trainable.bg) and (path_idx == 0):
474
+ para['bg'] = [para_bg]
475
+ para['point'] = point_var
476
+ para['color'] = color_var
477
+ if cfg.trainable.stroke:
478
+ para['stroke_width'] = stroke_width_var
479
+ para['stroke_color'] = stroke_color_var
480
+
481
+ pg = [{'params' : para[ki], 'lr' : cfg.lr_base[ki]} for ki in sorted(para.keys())]
482
+ optim = torch.optim.Adam(pg)
483
+
484
+ if cfg.trainable.record:
485
+ scheduler = LambdaLR(
486
+ optim, lr_lambda=lrlambda_f, last_epoch=-1)
487
+ else:
488
+ scheduler = LambdaLR(
489
+ optim, lr_lambda=lrlambda_f, last_epoch=cfg.num_iter)
490
+ optim_schedular_dict[path_idx] = (optim, scheduler)
491
+
492
+ # Inner loop training
493
+ t_range = tqdm(range(cfg.num_iter))
494
+ for t in t_range:
495
+
496
+ for _, (optim, _) in optim_schedular_dict.items():
497
+ optim.zero_grad()
498
+
499
+ # Forward pass: render the image.
500
+ scene_args = pydiffvg.RenderFunction.serialize_scene(
501
+ w, h, shapes_record, shape_groups_record)
502
+ img = render(w, h, 2, 2, t, None, *scene_args)
503
+
504
+ # Compose img with white background
505
+ img = img[:, :, 3:4] * img[:, :, :3] + \
506
+ para_bg * (1 - img[:, :, 3:4])
507
+
508
+ if cfg.save.video:
509
+ filename = os.path.join(
510
+ cfg.experiment_dir, "video-png",
511
+ "{}-iter{}.png".format(pathn_record_str, t))
512
+ check_and_create_dir(filename)
513
+ if cfg.use_ycrcb:
514
+ imshow = ycrcb_conversion(
515
+ img, format='[2D x 3]', reverse=True).detach().cpu()
516
+ else:
517
+ imshow = img.detach().cpu()
518
+ pydiffvg.imwrite(imshow, filename, gamma=gamma)
519
+
520
+ x = img.unsqueeze(0).permute(0, 3, 1, 2) # HWC -> NCHW
521
+
522
+ if cfg.use_ycrcb:
523
+ color_reweight = torch.FloatTensor([255/219, 255/224, 255/255]).to(device)
524
+ loss = ((x-gt)*(color_reweight.view(1, -1, 1, 1)))**2
525
+ else:
526
+ loss = ((x-gt)**2)
527
+
528
+ if cfg.loss.use_l1_loss:
529
+ loss = abs(x-gt)
530
+
531
+ if cfg.loss.use_distance_weighted_loss:
532
+ if cfg.use_ycrcb:
533
+ raise ValueError
534
+ shapes_forsdf = copy.deepcopy(shapes)
535
+ shape_groups_forsdf = copy.deepcopy(shape_groups)
536
+ for si in shapes_forsdf:
537
+ si.stroke_width = torch.FloatTensor([0]).to(device)
538
+ for sg_idx, sgi in enumerate(shape_groups_forsdf):
539
+ sgi.fill_color = torch.FloatTensor([1, 1, 1, 1]).to(device)
540
+ sgi.shape_ids = torch.LongTensor([sg_idx]).to(device)
541
+
542
+ sargs_forsdf = pydiffvg.RenderFunction.serialize_scene(
543
+ w, h, shapes_forsdf, shape_groups_forsdf)
544
+ with torch.no_grad():
545
+ im_forsdf = render(w, h, 2, 2, 0, None, *sargs_forsdf)
546
+ # use alpha channel is a trick to get 0-1 image
547
+ im_forsdf = (im_forsdf[:, :, 3]).detach().cpu().numpy()
548
+ loss_weight = get_sdf(im_forsdf, normalize='to1')
549
+ loss_weight += loss_weight_keep
550
+ loss_weight = np.clip(loss_weight, 0, 1)
551
+ loss_weight = torch.FloatTensor(loss_weight).to(device)
552
+
553
+ if cfg.save.loss:
554
+ save_loss = loss.squeeze(dim=0).mean(dim=0,keepdim=False).cpu().detach().numpy()
555
+ save_weight = loss_weight.cpu().detach().numpy()
556
+ save_weighted_loss = save_loss*save_weight
557
+ # normalize to [0,1]
558
+ save_loss = (save_loss - np.min(save_loss))/np.ptp(save_loss)
559
+ save_weight = (save_weight - np.min(save_weight))/np.ptp(save_weight)
560
+ save_weighted_loss = (save_weighted_loss - np.min(save_weighted_loss))/np.ptp(save_weighted_loss)
561
+
562
+ # save
563
+ plt.imshow(save_loss, cmap='Reds')
564
+ plt.axis('off')
565
+ # plt.colorbar()
566
+ filename = os.path.join(cfg.experiment_dir, "loss", "{}-iter{}-mseloss.png".format(pathn_record_str, t))
567
+ check_and_create_dir(filename)
568
+ plt.savefig(filename, dpi=800)
569
+ plt.close()
570
+
571
+ plt.imshow(save_weight, cmap='Greys')
572
+ plt.axis('off')
573
+ # plt.colorbar()
574
+ filename = os.path.join(cfg.experiment_dir, "loss", "{}-iter{}-sdfweight.png".format(pathn_record_str, t))
575
+ plt.savefig(filename, dpi=800)
576
+ plt.close()
577
+
578
+ plt.imshow(save_weighted_loss, cmap='Reds')
579
+ plt.axis('off')
580
+ # plt.colorbar()
581
+ filename = os.path.join(cfg.experiment_dir, "loss", "{}-iter{}-weightedloss.png".format(pathn_record_str, t))
582
+ plt.savefig(filename, dpi=800)
583
+ plt.close()
584
+
585
+
586
+
587
+
588
+
589
+ if loss_weight is None:
590
+ loss = loss.sum(1).mean()
591
+ else:
592
+ loss = (loss.sum(1)*loss_weight).mean()
593
+
594
+ # if (cfg.loss.bis_loss_weight is not None) and (cfg.loss.bis_loss_weight > 0):
595
+ # loss_bis = bezier_intersection_loss(point_var[0]) * cfg.loss.bis_loss_weight
596
+ # loss = loss + loss_bis
597
+ if (cfg.loss.xing_loss_weight is not None) \
598
+ and (cfg.loss.xing_loss_weight > 0):
599
+ loss_xing = xing_loss(point_var) * cfg.loss.xing_loss_weight
600
+ loss = loss + loss_xing
601
+
602
+
603
+ loss_list.append(loss.item())
604
+ t_range.set_postfix({'loss': loss.item()})
605
+ loss.backward()
606
+
607
+ # step
608
+ for _, (optim, scheduler) in optim_schedular_dict.items():
609
+ optim.step()
610
+ scheduler.step()
611
+
612
+ for group in shape_groups_record:
613
+ group.fill_color.data.clamp_(0.0, 1.0)
614
+
615
+ if cfg.loss.use_distance_weighted_loss:
616
+ loss_weight_keep = loss_weight.detach().cpu().numpy() * 1
617
+
618
+ if not cfg.trainable.record:
619
+ for _, pi in pg.items():
620
+ for ppi in pi:
621
+ pi.require_grad = False
622
+ optim_schedular_dict = {}
623
+
624
+ if cfg.save.image:
625
+ filename = os.path.join(
626
+ cfg.experiment_dir, "demo-png", "{}.png".format(pathn_record_str))
627
+ check_and_create_dir(filename)
628
+ if cfg.use_ycrcb:
629
+ imshow = ycrcb_conversion(
630
+ img, format='[2D x 3]', reverse=True).detach().cpu()
631
+ else:
632
+ imshow = img.detach().cpu()
633
+ pydiffvg.imwrite(imshow, filename, gamma=gamma)
634
+
635
+ if cfg.save.output:
636
+ filename = os.path.join(
637
+ cfg.experiment_dir, "output-svg", "{}.svg".format(pathn_record_str))
638
+ check_and_create_dir(filename)
639
+ pydiffvg.save_svg(filename, w, h, shapes_record, shape_groups_record)
640
+
641
+ loss_matrix.append(loss_list)
642
+
643
+ # calculate the pixel loss
644
+ # pixel_loss = ((x-gt)**2).sum(dim=1, keepdim=True).sqrt_() # [N,1,H, W]
645
+ # region_loss = adaptive_avg_pool2d(pixel_loss, cfg.region_loss_pool_size)
646
+ # loss_weight = torch.softmax(region_loss.reshape(1, 1, -1), dim=-1)\
647
+ # .reshape_as(region_loss)
648
+
649
+ pos_init_method = naive_coord_init(x, gt)
650
+
651
+ if cfg.coord_init.type == 'naive':
652
+ pos_init_method = naive_coord_init(x, gt)
653
+ elif cfg.coord_init.type == 'sparse':
654
+ pos_init_method = sparse_coord_init(x, gt)
655
+ elif cfg.coord_init.type == 'random':
656
+ pos_init_method = random_coord_init([h, w])
657
+ else:
658
+ raise ValueError
659
+
660
+ if cfg.save.video:
661
+ print("saving iteration video...")
662
+ img_array = []
663
+ for ii in range(0, cfg.num_iter):
664
+ filename = os.path.join(
665
+ cfg.experiment_dir, "video-png",
666
+ "{}-iter{}.png".format(pathn_record_str, ii))
667
+ img = cv2.imread(filename)
668
+ # cv2.putText(
669
+ # img, "Path:{} \nIteration:{}".format(pathn_record_str, ii),
670
+ # (10, 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 1)
671
+ img_array.append(img)
672
+
673
+ videoname = os.path.join(
674
+ cfg.experiment_dir, "video-mp4",
675
+ "{}.mp4".format(pathn_record_str))
676
+ check_and_create_dir(videoname)
677
+ out = cv2.VideoWriter(
678
+ videoname,
679
+ cv2.VideoWriter_fourcc(*'mp4v'),
680
+ # cv2.VideoWriter_fourcc(*'FFV1'),
681
+ 20.0, (w, h))
682
+ for iii in range(len(img_array)):
683
+ out.write(img_array[iii])
684
+ out.release()
685
+ # shutil.rmtree(os.path.join(cfg.experiment_dir, "video-png"))
686
+
687
+ print("The last loss is: {}".format(loss.item()))
LIVE/env.yml ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: live
2
+ channels:
3
+ - pytorch
4
+ - anaconda
5
+ - conda-forge
6
+ - defaults
7
+ dependencies:
8
+ - _libgcc_mutex=0.1=main
9
+ - _openmp_mutex=4.5=1_gnu
10
+ - blas=1.0=mkl
11
+ - bzip2=1.0.8=h7b6447c_0
12
+ - ca-certificates=2021.5.30=ha878542_0
13
+ - certifi=2021.5.30=py37h06a4308_0
14
+ - cloudpickle=1.6.0=py_0
15
+ - cmake=3.18.2=ha30ef3c_0
16
+ - cudatoolkit=10.2.89=hfd86e86_1
17
+ - cycler=0.10.0=py37_0
18
+ - cytoolz=0.11.0=py37h7b6447c_0
19
+ - dask-core=2021.6.2=pyhd3eb1b0_0
20
+ - decorator=5.0.9=pyhd3eb1b0_0
21
+ - expat=2.2.10=he6710b0_2
22
+ - ffmpeg=4.3=hf484d3e_0
23
+ - freetype=2.10.4=h5ab3b9f_0
24
+ - gmp=6.2.1=h2531618_2
25
+ - gnutls=3.6.15=he1e5248_0
26
+ - imageio=2.9.0=pyhd3eb1b0_0
27
+ - intel-openmp=2021.2.0=h06a4308_610
28
+ - jpeg=9b=h024ee3a_2
29
+ - kiwisolver=1.3.1=py37h2531618_0
30
+ - krb5=1.18.2=h173b8e3_0
31
+ - lame=3.100=h7b6447c_0
32
+ - lcms2=2.12=h3be6417_0
33
+ - ld_impl_linux-64=2.35.1=h7274673_9
34
+ - libcurl=7.71.1=h20c2e04_1
35
+ - libedit=3.1.20191231=h14c3975_1
36
+ - libffi=3.3=he6710b0_2
37
+ - libgcc-ng=9.3.0=h5101ec6_17
38
+ - libgfortran-ng=7.5.0=ha8ba4b0_17
39
+ - libgfortran4=7.5.0=ha8ba4b0_17
40
+ - libgomp=9.3.0=h5101ec6_17
41
+ - libiconv=1.15=h63c8f33_5
42
+ - libidn2=2.3.1=h27cfd23_0
43
+ - libpng=1.6.37=hbc83047_0
44
+ - libssh2=1.9.0=h1ba5d50_1
45
+ - libstdcxx-ng=9.3.0=hd4cf53a_17
46
+ - libtasn1=4.16.0=h27cfd23_0
47
+ - libtiff=4.2.0=h85742a9_0
48
+ - libunistring=0.9.10=h27cfd23_0
49
+ - libuv=1.40.0=h7b6447c_0
50
+ - libwebp-base=1.2.0=h27cfd23_0
51
+ - locket=0.2.1=py37h06a4308_1
52
+ - lz4-c=1.9.3=h2531618_0
53
+ - matplotlib-base=3.3.4=py37h62a2d02_0
54
+ - mkl=2021.2.0=h06a4308_296
55
+ - mkl-service=2.3.0=py37h27cfd23_1
56
+ - mkl_fft=1.3.0=py37h42c9631_2
57
+ - mkl_random=1.2.1=py37ha9443f7_2
58
+ - ncurses=6.2=he6710b0_1
59
+ - nettle=3.7.3=hbbd107a_1
60
+ - networkx=2.2=py37_1
61
+ - ninja=1.10.2=hff7bd54_1
62
+ - numpy=1.20.2=py37h2d18471_0
63
+ - numpy-base=1.20.2=py37hfae3a4d_0
64
+ - olefile=0.46=py37_0
65
+ - openh264=2.1.0=hd408876_0
66
+ - openssl=1.1.1k=h27cfd23_0
67
+ - partd=1.2.0=pyhd3eb1b0_0
68
+ - pillow=8.2.0=py37he98fc37_0
69
+ - pip=21.1.3=py37h06a4308_0
70
+ - pyparsing=2.4.7=pyhd3eb1b0_0
71
+ - python=3.7.10=h12debd9_4
72
+ - python-dateutil=2.8.1=pyhd3eb1b0_0
73
+ - pytorch=1.9.0=py3.7_cuda10.2_cudnn7.6.5_0
74
+ - pywavelets=1.1.1=py37h7b6447c_2
75
+ - pyyaml=5.4.1=py37h27cfd23_1
76
+ - readline=8.1=h27cfd23_0
77
+ - rhash=1.4.0=h1ba5d50_0
78
+ - scikit-image=0.18.1=py37ha9443f7_0
79
+ - scipy=1.6.2=py37had2a1c9_1
80
+ - setuptools=52.0.0=py37h06a4308_0
81
+ - six=1.16.0=pyhd3eb1b0_0
82
+ - sqlite=3.36.0=hc218d9a_0
83
+ - tifffile=2020.10.1=py37hdd07704_2
84
+ - tk=8.6.10=hbc83047_0
85
+ - toolz=0.11.1=pyhd3eb1b0_0
86
+ - torchvision=0.10.0=py37_cu102
87
+ - tornado=6.1=py37h27cfd23_0
88
+ - typing_extensions=3.10.0.0=pyh06a4308_0
89
+ - wheel=0.36.2=pyhd3eb1b0_0
90
+ - xz=5.2.5=h7b6447c_0
91
+ - yaml=0.2.5=h7b6447c_0
92
+ - zlib=1.2.11=h7b6447c_3
93
+ - zstd=1.4.5=h9ceee32_0
94
+ - pip:
95
+ - absl-py==0.13.0
96
+ - aiohttp==3.7.4.post0
97
+ - async-timeout==3.0.1
98
+ - attrs==21.2.0
99
+ - cachetools==4.2.2
100
+ - cffi==1.14.5
101
+ - chardet==4.0.0
102
+ - coloredlogs==15.0.1
103
+ - cssutils==2.3.0
104
+ - diffvg==0.0.1
105
+ - easydict==1.9
106
+ - einops==0.3.0
107
+ - fsspec==2021.6.1
108
+ - future==0.18.2
109
+ - google-auth==1.32.1
110
+ - google-auth-oauthlib==0.4.4
111
+ - greenlet==1.1.0
112
+ - grpcio==1.38.1
113
+ - humanfriendly==9.2
114
+ - idna==2.10
115
+ - imageio-ffmpeg==0.4.4
116
+ - importlib-metadata==4.6.0
117
+ - jinja2==3.0.1
118
+ - jsonpatch==1.32
119
+ - jsonpointer==2.1
120
+ - kornia==0.1.4
121
+ - llvmlite==0.36.0
122
+ - markdown==3.3.4
123
+ - markupsafe==2.0.1
124
+ - multidict==5.1.0
125
+ - numba==0.53.1
126
+ - oauthlib==3.1.1
127
+ - opencv-python==4.5.3.56
128
+ - packaging==20.9
129
+ - pandas==1.3.0
130
+ - protobuf==3.17.3
131
+ - pyaml==20.4.0
132
+ - pyasn1==0.4.8
133
+ - pyasn1-modules==0.2.8
134
+ - pybind11==2.6.2
135
+ - pycparser==2.20
136
+ - pydeprecate==0.3.0
137
+ - pypng==0.0.20
138
+ - pytorch-lightning==1.3.8
139
+ - pytorch-ranger==0.1.1
140
+ - pytz==2021.1
141
+ - pyzmq==22.1.0
142
+ - requests==2.25.1
143
+ - requests-oauthlib==1.3.0
144
+ - rsa==4.7.2
145
+ - scikit-fmm==2021.10.29
146
+ - seaborn==0.11.1
147
+ - sqlalchemy==1.4.20
148
+ - svgpathtools==1.4.1
149
+ - svgwrite==1.4.1
150
+ - tensorboard==2.4.1
151
+ - tensorboard-plugin-wit==1.8.0
152
+ - torch-optimizer==0.0.1a15
153
+ - torch-tools==0.1.5
154
+ - torchfile==0.1.0
155
+ - torchmetrics==0.4.0
156
+ - tqdm==4.61.1
157
+ - urllib3==1.26.6
158
+ - visdom==0.1.8.9
159
+ - websocket-client==1.1.0
160
+ - werkzeug==2.0.1
161
+ - yarl==1.6.3
162
+ - zipp==3.4.1
163
+ prefix: /home/UserName/.conda/envs/live
164
+
LIVE/example.png ADDED
LIVE/system_info.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ {'sys.platform': 'linux', 'Python': '3.7.10 (default, Jun 4 2021, 14:48:32) [GCC 7.5.0]', 'CUDA available': True, 'GPU 0': 'Tesla V100-SXM2-32GB', 'GCC': 'gcc (GCC) 8.1.0', 'PyTorch': '1.9.0', 'PyTorch compiling details': 'PyTorch built with:\n - GCC 7.3\n - C++ Version: 201402\n - Intel(R) oneAPI Math Kernel Library Version 2021.2-Product Build 20210312 for Intel(R) 64 architecture applications\n - Intel(R) MKL-DNN v2.1.2 (Git Hash 98be7e8afa711dc9b66c8ff3504129cb82013cdb)\n - OpenMP 201511 (a.k.a. OpenMP 4.5)\n - NNPACK is enabled\n - CPU capability usage: AVX2\n - CUDA Runtime 10.2\n - NVCC architecture flags: -gencode;arch=compute_37,code=sm_37;-gencode;arch=compute_50,code=sm_50;-gencode;arch=compute_60,code=sm_60;-gencode;arch=compute_61,code=sm_61;-gencode;arch=compute_70,code=sm_70;-gencode;arch=compute_75,code=sm_75;-gencode;arch=compute_37,code=compute_37\n - CuDNN 7.6.5\n - Magma 2.5.2\n - Build settings: BLAS_INFO=mkl, BUILD_TYPE=Release, CUDA_VERSION=10.2, CUDNN_VERSION=7.6.5, CXX_COMPILER=/opt/rh/devtoolset-7/root/usr/bin/c++, CXX_FLAGS= -Wno-deprecated -fvisibility-inlines-hidden -DUSE_PTHREADPOOL -fopenmp -DNDEBUG -DUSE_KINETO -DUSE_FBGEMM -DUSE_QNNPACK -DUSE_PYTORCH_QNNPACK -DUSE_XNNPACK -DSYMBOLICATE_MOBILE_DEBUG_HANDLE -O2 -fPIC -Wno-narrowing -Wall -Wextra -Werror=return-type -Wno-missing-field-initializers -Wno-type-limits -Wno-array-bounds -Wno-unknown-pragmas -Wno-sign-compare -Wno-unused-parameter -Wno-unused-variable -Wno-unused-function -Wno-unused-result -Wno-unused-local-typedefs -Wno-strict-overflow -Wno-strict-aliasing -Wno-error=deprecated-declarations -Wno-stringop-overflow -Wno-psabi -Wno-error=pedantic -Wno-error=redundant-decls -Wno-error=old-style-cast -fdiagnostics-color=always -faligned-new -Wno-unused-but-set-variable -Wno-maybe-uninitialized -fno-math-errno -fno-trapping-math -Werror=format -Wno-stringop-overflow, LAPACK_INFO=mkl, PERF_WITH_AVX=1, PERF_WITH_AVX2=1, PERF_WITH_AVX512=1, TORCH_VERSION=1.9.0, USE_CUDA=ON, USE_CUDNN=ON, USE_EXCEPTION_PTR=1, USE_GFLAGS=OFF, USE_GLOG=OFF, USE_MKL=ON, USE_MKLDNN=ON, USE_MPI=OFF, USE_NCCL=ON, USE_NNPACK=ON, USE_OPENMP=ON, \n', 'TorchVision': '0.10.0'}
LIVE/user_study_state.csv ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Page 1,,
2
+ ,,
3
+ "1. Please carefully select the method that best rebuilds the original image ""progressively""", showing a human-like interpretation.,
4
+ Option,Percentage%,Count
5
+ DiffVG,20.00%,4
6
+ Painting,25.00%,5
7
+ LIVE,55.00%,11
8
+ Total,,20
9
+ ,,
10
+ 2. Same question,,
11
+ Option,Percentage%,Count
12
+ DiffVG,25.00%,5
13
+ Painting,15.00%,3
14
+ LIVE,60.00%,12
15
+ Total,,20
16
+ ,,
17
+ 3. Same question,,
18
+ Option,Percentage%,Count
19
+ DiffVG,10.00%,2
20
+ Painting,10.00%,2
21
+ LIVE,80.00%,16
22
+ Total,,20
23
+ ,,
24
+ 4. Same question,,
25
+ Option,Percentage%,Count
26
+ DiffVG,40.00%,8
27
+ Painting,0.00%,0
28
+ LIVE,60.00%,12
29
+ Total,,20
30
+ ,,
31
+ 5. Same question,,
32
+ Option,Percentage%,Count
33
+ DiffVG,20.00%,4
34
+ Painting,5.00%,1
35
+ LIVE,75.00%,15
36
+ Total,,20
37
+ ,,
38
+ 6. Same Question,,
39
+ Option,Percentage%,Count
40
+ DiffVG,20.00%,4
41
+ Painting,15.00%,3
42
+ LIVE,65.00%,13
43
+ Total,,20
44
+ ,,
45
+ 7. Same question,,
46
+ Option,Percentage%,Count
47
+ DiffVG,5.00%,1
48
+ Painting,10.00%,2
49
+ LIVE,85.00%,17
50
+ Total,,20
51
+ ,,
52
+ 8. Same question,,
53
+ Option,Percentage%,Count
54
+ DiffVG,25.00%,5
55
+ Painting,10.00%,2
56
+ LIVE,65.00%,13
57
+ Total,,20
58
+ ,,
59
+ 9. Same question,,
60
+ Option,Percentage%,Count
61
+ DiffVG,15.00%,3
62
+ Painting,5.00%,1
63
+ LIVE,80.00%,16
64
+ Total,,20
65
+ ,,
66
+ 10. Same question,,
67
+ Option,Percentage%,Count
68
+ DiffVG,25.00%,5
69
+ Painting,5.00%,1
70
+ LIVE,70.00%,14
71
+ Total,,20
72
+ ,,
73
+ 11. Same question,,
74
+ Option,Percentage%,Count
75
+ DiffVG,10.00%,2
76
+ Painting,15.00%,3
77
+ LIVE,75.00%,15
78
+ Total,,20
79
+ ,,
80
+ 12. Same question,,
81
+ Option,Percentage%,Count
82
+ DiffVG,15.00%,3
83
+ Painting,10.00%,2
84
+ LIVE,75.00%,15
85
+ Total,,20
86
+ ,,
87
+ 13. Same question,,
88
+ Option,Percentage%,Count
89
+ DiffVG,25.00%,5
90
+ Painting,15.00%,3
91
+ LIVE,60.00%,12
92
+ Total,,20
93
+ ,,
94
+ 14. Same question,,
95
+ Option,Percentage%,Count
96
+ DiffVG,5.00%,1
97
+ Painting,15.00%,3
98
+ LIVE,80.00%,16
99
+ Total,,20
100
+ ,,
101
+ 15. Same question,,
102
+ Option,Percentage%,Count
103
+ DiffVG,40.00%,8
104
+ Painting,5.00%,1
105
+ LIVE,55.00%,11
106
+ Total,,20
107
+ ,,
108
+ 16. Same question,,
109
+ Option,Percentage%,Count
110
+ DiffVG,0.00%,0
111
+ Painting,15.00%,3
112
+ LIVE,85.00%,17
113
+ Total,,20
114
+ ,,
115
+ 17. Same question,,
116
+ Option,Percentage%,Count
117
+ DiffVG,0.00%,0
118
+ Painting,15.00%,3
119
+ LIVE,85.00%,17
120
+ Total,,20
121
+ ,,
122
+ 18. Same question,,
123
+ Option,Percentage%,Count
124
+ DiffVG,0.00%,0
125
+ Painting,15.00%,3
126
+ LIVE,85.00%,17
127
+ Total,,20
128
+ ,,
129
+ 19. Same question,,
130
+ Option,Percentage%,Count
131
+ DiffVG,0.00%,0
132
+ Painting,15.00%,3
133
+ LIVE,85.00%,17
134
+ Total,,20
135
+ ,,
136
+ 20. Same question,,
137
+ Option,Percentage%,Count
138
+ DiffVG,0.00%,0
139
+ Painting,15.00%,3
140
+ LIVE,85.00%,17
141
+ Total,,20
142
+ ,,
143
+ 21. Same question,,
144
+ Option,Percentage%,Count
145
+ DiffVG,0.00%,0
146
+ Painting,15.00%,3
147
+ LIVE,85.00%,17
148
+ Total,,20
README.md CHANGED
@@ -1,13 +1,14 @@
1
  ---
2
  title: LIVE
3
- emoji: 📈
4
- colorFrom: purple
5
- colorTo: pink
6
  sdk: gradio
7
- sdk_version: 3.0.13
8
  app_file: app.py
9
  pinned: false
10
- license: mit
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
1
  ---
2
  title: LIVE
3
+ emoji: 📊
4
+ colorFrom: pink
5
+ colorTo: indigo
6
  sdk: gradio
7
+ sdk_version: 2.9.1
8
  app_file: app.py
9
  pinned: false
10
+ license: gpl-3.0
11
  ---
12
 
13
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces#reference
14
+
__init__.py ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ __author__ = "Xu Ma"
2
+ __email__ = "ma.xu1@northeastern.edu"
aabb.h ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #pragma once
2
+
3
+ #include "diffvg.h"
4
+ #include "cuda_utils.h"
5
+ #include "vector.h"
6
+ #include "matrix.h"
7
+
8
+ struct AABB {
9
+ DEVICE
10
+ inline AABB(const Vector2f &p_min = Vector2f{infinity<float>(), infinity<float>()},
11
+ const Vector2f &p_max = Vector2f{-infinity<float>(), -infinity<float>()})
12
+ : p_min(p_min), p_max(p_max) {}
13
+ Vector2f p_min, p_max;
14
+ };
15
+
16
+ DEVICE
17
+ inline
18
+ AABB merge(const AABB &box, const Vector2f &p) {
19
+ return AABB{Vector2f{min(p.x, box.p_min.x), min(p.y, box.p_min.y)},
20
+ Vector2f{max(p.x, box.p_max.x), max(p.y, box.p_max.y)}};
21
+ }
22
+
23
+ DEVICE
24
+ inline
25
+ AABB merge(const AABB &box0, const AABB &box1) {
26
+ return AABB{Vector2f{min(box0.p_min.x, box1.p_min.x), min(box0.p_min.y, box1.p_min.y)},
27
+ Vector2f{max(box0.p_max.x, box1.p_max.x), max(box0.p_max.y, box1.p_max.y)}};
28
+ }
29
+
30
+ DEVICE
31
+ inline
32
+ bool inside(const AABB &box, const Vector2f &p) {
33
+ return p.x >= box.p_min.x && p.x <= box.p_max.x &&
34
+ p.y >= box.p_min.y && p.y <= box.p_max.y;
35
+ }
36
+
37
+ DEVICE
38
+ inline
39
+ bool inside(const AABB &box, const Vector2f &p, float radius) {
40
+ return p.x >= box.p_min.x - radius && p.x <= box.p_max.x + radius &&
41
+ p.y >= box.p_min.y - radius && p.y <= box.p_max.y + radius;
42
+ }
43
+
44
+ DEVICE
45
+ inline
46
+ AABB enlarge(const AABB &box, float width) {
47
+ return AABB{Vector2f{box.p_min.x - width, box.p_min.y - width},
48
+ Vector2f{box.p_max.x + width, box.p_max.y + width}};
49
+ }
50
+
51
+ DEVICE
52
+ inline
53
+ AABB transform(const Matrix3x3f &xform, const AABB &box) {
54
+ auto ret = AABB();
55
+ ret = merge(ret, xform_pt(xform, Vector2f{box.p_min.x, box.p_min.y}));
56
+ ret = merge(ret, xform_pt(xform, Vector2f{box.p_min.x, box.p_max.y}));
57
+ ret = merge(ret, xform_pt(xform, Vector2f{box.p_max.x, box.p_min.y}));
58
+ ret = merge(ret, xform_pt(xform, Vector2f{box.p_max.x, box.p_max.y}));
59
+ return ret;
60
+ }
61
+
62
+ DEVICE
63
+ inline
64
+ bool within_distance(const AABB &box, const Vector2f &pt, float r) {
65
+ return pt.x >= box.p_min.x - r && pt.x <= box.p_max.x + r &&
66
+ pt.y >= box.p_min.y - r && pt.y <= box.p_max.y + r;
67
+ }
app.py ADDED
@@ -0,0 +1,375 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ os.system('python setup.py install --user')
3
+ import argparse
4
+ import csv
5
+ import numpy as np
6
+ import sys
7
+ sys.path.append("/home/user/.local/lib/python3.8/site-packages/diffvg-0.0.1-py3.8-linux-x86_64.egg")
8
+ print(sys.path)
9
+ from pathlib import Path
10
+
11
+ import gradio as gr
12
+
13
+ import torch
14
+ import yaml
15
+ from PIL import Image
16
+ from subprocess import call
17
+ import torch
18
+ import cv2
19
+ import matplotlib.pyplot as plt
20
+ import random
21
+ import argparse
22
+ import math
23
+ import errno
24
+ from tqdm import tqdm
25
+ import yaml
26
+ from easydict import EasyDict as edict
27
+
28
+
29
+ def run_cmd(command):
30
+ try:
31
+ print(command)
32
+ call(command, shell=True)
33
+ except KeyboardInterrupt:
34
+ print("Process interrupted")
35
+ sys.exit(1)
36
+ # run_cmd("gcc --version")
37
+ # run_cmd("pwd")
38
+ # run_cmd("ls")
39
+ # run_cmd("git submodule update --init --recursive")
40
+ # run_cmd("python setup.py install --user")
41
+ # run_cmd("pip3 list")
42
+ # import pydiffvg
43
+ #
44
+ # print("Sccuessfuly import diffvg ")
45
+ # run_cmd("pwd")
46
+ # run_cmd("ls")
47
+ # run_cmd("git submodule update --init --recursive")
48
+ # run_cmd("python setup.py install --user")
49
+
50
+ # run_cmd("python main.py --config config/base.yaml --experiment experiment_5x1 --signature smile --target figures/smile.png --log_dir log/")
51
+ from main import main_func
52
+
53
+
54
+ def parse_args():
55
+ parser = argparse.ArgumentParser()
56
+ parser.add_argument('--debug', action='store_true', default=False)
57
+ parser.add_argument("--config", default="config/base.yaml", type=str)
58
+ parser.add_argument("--experiment", type=str)
59
+ parser.add_argument("--seed", type=int)
60
+ parser.add_argument("--target", type=str, help="target image path")
61
+ parser.add_argument('--log_dir', metavar='DIR', default="log/")
62
+ parser.add_argument('--initial', type=str, default="random", choices=['random', 'circle'])
63
+ parser.add_argument('--signature', default="demo", nargs='+', type=str)
64
+ parser.add_argument('--seginit', nargs='+', type=str)
65
+ parser.add_argument("--num_segments", type=int, default=4)
66
+ # parser.add_argument("--num_paths", type=str, default="1,1,1")
67
+ # parser.add_argument("--num_iter", type=int, default=500)
68
+ # parser.add_argument('--free', action='store_true')
69
+ # Please ensure that image resolution is divisible by pool_size; otherwise the performance would drop a lot.
70
+ # parser.add_argument('--pool_size', type=int, default=40, help="the pooled image size for next path initialization")
71
+ # parser.add_argument('--save_loss', action='store_true')
72
+ # parser.add_argument('--save_init', action='store_true')
73
+ # parser.add_argument('--save_image', action='store_true')
74
+ # parser.add_argument('--save_video', action='store_true')
75
+ # parser.add_argument('--print_weight', action='store_true')
76
+ # parser.add_argument('--circle_init_radius', type=float)
77
+ cfg = edict()
78
+ args = parser.parse_args()
79
+ cfg.debug = args.debug
80
+ cfg.config = args.config
81
+ cfg.experiment = args.experiment
82
+ cfg.seed = args.seed
83
+ cfg.target = args.target
84
+ cfg.log_dir = args.log_dir
85
+ cfg.initial = args.initial
86
+ cfg.signature = args.signature
87
+ # set cfg num_segments in command
88
+ cfg.num_segments = args.num_segments
89
+ if args.seginit is not None:
90
+ cfg.seginit = edict()
91
+ cfg.seginit.type = args.seginit[0]
92
+ if cfg.seginit.type == 'circle':
93
+ cfg.seginit.radius = float(args.seginit[1])
94
+ return cfg
95
+
96
+
97
+ def app_experiment_change(experiment_id):
98
+ if experiment_id == "add [1] total 1 path for demonstration":
99
+ return "experiment_1x1"
100
+ if experiment_id == "add [1, 1, 1, 1, 1] total 5 paths one by one":
101
+ return "experiment_5x1"
102
+ elif experiment_id == "add [1, 1, 1, 1, 1, 1, 1, 1] total 8 paths one by one":
103
+ return "experiment_8x1"
104
+ elif experiment_id == "add [1,2,4,8,16,32, ...] total 128 paths":
105
+ return "experiment_exp2_128"
106
+ elif experiment_id == "add [1,2,4,8,16,32, ...] total 256 paths":
107
+ return "experiment_exp2_256"
108
+
109
+
110
+ cfg_arg = parse_args()
111
+ temp_image = np.random.rand(224,224,3)
112
+ temp_text = "start"
113
+ temp_input = np.random.rand(224,224,3)
114
+ def run_live(img, experiment_id, num_iter, cfg_arg=cfg_arg):
115
+ experiment = app_experiment_change(experiment_id)
116
+ cfg_arg.target = img
117
+ cfg_arg.experiment = experiment
118
+ img, text = main_func(img, experiment_id, num_iter, cfg_arg=cfg_arg)
119
+ return img, text
120
+
121
+
122
+
123
+
124
+
125
+
126
+
127
+
128
+
129
+ # ROOT_PATH = sys.path[0] # 根目录
130
+ # # 模型路径
131
+ # model_path = "ultralytics/yolov5"
132
+ # # 模型名称临时变量
133
+ # model_name_tmp = ""
134
+ # # 设备临时变量
135
+ # device_tmp = ""
136
+ # # 文件后缀
137
+ # suffix_list = [".csv", ".yaml"]
138
+ # def parse_args(known=False):
139
+ # parser = argparse.ArgumentParser(description="Gradio LIVE")
140
+ # parser.add_argument(
141
+ # "--model_name", "-mn", default="yolov5s", type=str, help="model name"
142
+ # )
143
+ # parser.add_argument(
144
+ # "--model_cfg",
145
+ # "-mc",
146
+ # default="./model_config/model_name_p5_all.yaml",
147
+ # type=str,
148
+ # help="model config",
149
+ # )
150
+ # parser.add_argument(
151
+ # "--cls_name",
152
+ # "-cls",
153
+ # default="./cls_name/cls_name.yaml",
154
+ # type=str,
155
+ # help="cls name",
156
+ # )
157
+ # parser.add_argument(
158
+ # "--nms_conf",
159
+ # "-conf",
160
+ # default=0.5,
161
+ # type=float,
162
+ # help="model NMS confidence threshold",
163
+ # )
164
+ # parser.add_argument(
165
+ # "--nms_iou", "-iou", default=0.45, type=float, help="model NMS IoU threshold"
166
+ # )
167
+ #
168
+ # parser.add_argument(
169
+ # "--label_dnt_show",
170
+ # "-lds",
171
+ # action="store_false",
172
+ # default=True,
173
+ # help="label show",
174
+ # )
175
+ # parser.add_argument(
176
+ # "--device",
177
+ # "-dev",
178
+ # default="cpu",
179
+ # type=str,
180
+ # help="cuda or cpu, hugging face only cpu",
181
+ # )
182
+ # parser.add_argument(
183
+ # "--inference_size", "-isz", default=640, type=int, help="model inference size"
184
+ # )
185
+ #
186
+ # args = parser.parse_known_args()[0] if known else parser.parse_args()
187
+ # return args
188
+ # # 模型加载
189
+ # def model_loading(model_name, device):
190
+ #
191
+ # # 加载本地模型
192
+ # model = torch.hub.load(model_path, model_name, force_reload=True, device=device)
193
+ #
194
+ # return model
195
+ # # 检测信息
196
+ # def export_json(results, model, img_size):
197
+ #
198
+ # return [
199
+ # [
200
+ # {
201
+ # "id": int(i),
202
+ # "class": int(result[i][5]),
203
+ # "class_name": model.model.names[int(result[i][5])],
204
+ # "normalized_box": {
205
+ # "x0": round(result[i][:4].tolist()[0], 6),
206
+ # "y0": round(result[i][:4].tolist()[1], 6),
207
+ # "x1": round(result[i][:4].tolist()[2], 6),
208
+ # "y1": round(result[i][:4].tolist()[3], 6),
209
+ # },
210
+ # "confidence": round(float(result[i][4]), 2),
211
+ # "fps": round(1000 / float(results.t[1]), 2),
212
+ # "width": img_size[0],
213
+ # "height": img_size[1],
214
+ # }
215
+ # for i in range(len(result))
216
+ # ]
217
+ # for result in results.xyxyn
218
+ # ]
219
+ # def yolo_det(img, experiment_id, device=None, model_name=None, inference_size=None, conf=None, iou=None, label_opt=None, model_cls=None):
220
+ #
221
+ # global model, model_name_tmp, device_tmp
222
+ #
223
+ # if model_name_tmp != model_name:
224
+ # # 模型判断,避免反复加载
225
+ # model_name_tmp = model_name
226
+ # model = model_loading(model_name_tmp, device)
227
+ # elif device_tmp != device:
228
+ # device_tmp = device
229
+ # model = model_loading(model_name_tmp, device)
230
+ #
231
+ # # -----------模型调参-----------
232
+ # model.conf = conf # NMS 置信度阈值
233
+ # model.iou = iou # NMS IOU阈值
234
+ # model.max_det = 1000 # 最大检测框数
235
+ # model.classes = model_cls # 模型类别
236
+ #
237
+ # results = model(img, size=inference_size) # 检测
238
+ # results.render(labels=label_opt) # 渲染
239
+ #
240
+ # det_img = Image.fromarray(results.imgs[0]) # 检测图片
241
+ #
242
+ # det_json = export_json(results, model, img.size)[0] # 检测信息
243
+ #
244
+ # return det_img, det_json
245
+
246
+
247
+ # def run_cmd(command):
248
+ # try:
249
+ # print(command)
250
+ # call(command, shell=True)
251
+ # except KeyboardInterrupt:
252
+ # print("Process interrupted")
253
+ # sys.exit(1)
254
+ #
255
+ # run_cmd("gcc --version")
256
+ # run_cmd("pwd")
257
+ # run_cmd("ls")
258
+ # run_cmd("git submodule update --init --recursive")
259
+ # run_cmd("python setup.py install --user")
260
+ # run_cmd("ls")
261
+ # run_cmd("python main.py --config config/base.yaml --experiment experiment_5x1 --signature smile --target figures/smile.png --log_dir log/")
262
+
263
+
264
+
265
+
266
+
267
+
268
+ # # yaml文件解析
269
+ # def yaml_parse(file_path):
270
+ # return yaml.safe_load(open(file_path, "r", encoding="utf-8").read())
271
+ #
272
+ #
273
+ # # yaml csv 文件解析
274
+ # def yaml_csv(file_path, file_tag):
275
+ # file_suffix = Path(file_path).suffix
276
+ # if file_suffix == suffix_list[0]:
277
+ # # 模型名称
278
+ # file_names = [i[0] for i in list(csv.reader(open(file_path)))] # csv版
279
+ # elif file_suffix == suffix_list[1]:
280
+ # # 模型名称
281
+ # file_names = yaml_parse(file_path).get(file_tag) # yaml版
282
+ # else:
283
+ # print(f"{file_path}格式不正确!程序退出!")
284
+ # sys.exit()
285
+ #
286
+ # return file_names
287
+
288
+
289
+ def main(args):
290
+ gr.close_all()
291
+ # -------------------Inputs-------------------
292
+ inputs_iteration = gr.inputs.Slider(
293
+ label="Optimization Iteration",
294
+ default=500, maximum=600, minimum=100, step=100)
295
+ inputs_img = gr.inputs.Image(type="pil", label="Input Image", shape=[160, 160])
296
+ experiment_id = gr.inputs.Radio(
297
+ choices=[
298
+ "add [1] total 1 path for demonstration",
299
+ "add [1, 1, 1, 1, 1] total 5 paths one by one",
300
+ "add [1, 1, 1, 1, 1, 1, 1, 1] total 8 paths one by one",
301
+ "add [1,2,4,8,16,32, ...] total 128 paths",
302
+ "add [1,2,4,8,16,32, ...] total 256 paths"], type="value", default="add [1, 1, 1, 1, 1] total 5 paths one by one", label="Path Adding Scheduler"
303
+ )
304
+
305
+ # inputs
306
+ inputs = [
307
+
308
+ inputs_img, # input image
309
+ experiment_id, # path adding scheduler
310
+ inputs_iteration, # input iteration
311
+
312
+ ]
313
+ # outputs
314
+ outputs = gr.outputs.Image(type="numpy", label="Vectorized Image")
315
+ outputs02 = gr.outputs.File(label="Generated SVG output")
316
+
317
+ # title
318
+ title = "LIVE: Towards Layer-wise Image Vectorization"
319
+ # description
320
+ description = "<div align='center'>(CVPR 2022 Oral Presentation)</div>" \
321
+ "<div align='center'>Without GPUs, LIVE will cost longer time.</div>" \
322
+ "<div align='center'>For efficiency, we rescale input to 160x160 (smaller size and fewer iterations will decrease the reconstructions).</div> "
323
+
324
+ # examples
325
+ examples = [
326
+ [
327
+ "./examples/1.png",
328
+ "add [1] total 1 path for demonstration",
329
+ 100,
330
+ ],
331
+ [
332
+ "./examples/2.png",
333
+ "add [1, 1, 1, 1, 1] total 5 paths one by one",
334
+ 300,
335
+ ],
336
+ [
337
+ "./examples/3.jpg",
338
+ "add [1,2,4,8,16,32, ...] total 128 paths",
339
+ 300,
340
+ ],
341
+ [
342
+ "./examples/4.png",
343
+ "add [1,2,4,8,16,32, ...] total 256 paths",
344
+ 300,
345
+ ],
346
+ [
347
+ "./examples/5.png",
348
+ "add [1, 1, 1, 1, 1] total 5 paths one by one",
349
+ 300,
350
+ ],
351
+ ]
352
+
353
+ # Interface
354
+ gr.Interface(
355
+ fn=run_live,
356
+ inputs=inputs,
357
+ outputs=[outputs, outputs02],
358
+ title=title,
359
+ description=description,
360
+ examples=examples,
361
+ theme="seafoam",
362
+ # live=True, # 实时变更输出
363
+ flagging_dir="log" # 输出目录
364
+ # ).launch(inbrowser=True, auth=['admin', 'admin'])
365
+ ).launch(
366
+ inbrowser=True, # 自动打开默认浏览器
367
+ show_tips=True, # 自动显示gradio最新功能
368
+ enable_queue=True
369
+ # favicon_path="./icon/logo.ico",
370
+ )
371
+
372
+
373
+ if __name__ == "__main__":
374
+ args = parse_args()
375
+ main(args)
atomic.cpp ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //A hacky solution to get around the Ellipse include
2
+
3
+ #ifdef WIN32
4
+ #include <windows.h>
5
+ #include <cstdint>
6
+
7
+ float win_atomic_add(float &target, float source) {
8
+ union { int i; float f; } old_val;
9
+ union { int i; float f; } new_val;
10
+ do {
11
+ old_val.f = target;
12
+ new_val.f = old_val.f + (float)source;
13
+ } while (InterlockedCompareExchange((LONG*)&target, (LONG)new_val.i, (LONG)old_val.i) != old_val.i);
14
+ return old_val.f;
15
+ }
16
+
17
+ double win_atomic_add(double &target, double source) {
18
+ union { int64_t i; double f; } old_val;
19
+ union { int64_t i; double f; } new_val;
20
+ do {
21
+ old_val.f = target;
22
+ new_val.f = old_val.f + (double)source;
23
+ } while (InterlockedCompareExchange64((LONG64*)&target, (LONG64)new_val.i, (LONG64)old_val.i) != old_val.i);
24
+ return old_val.f;
25
+ }
26
+
27
+ #endif
atomic.h ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #pragma once
2
+
3
+ #include "diffvg.h"
4
+ #include "vector.h"
5
+ #include "matrix.h"
6
+
7
+ // https://stackoverflow.com/questions/39274472/error-function-atomicadddouble-double-has-already-been-defined
8
+ #if !defined(__CUDA_ARCH__) || __CUDA_ARCH__ >= 600
9
+ #else
10
+ static inline DEVICE double atomicAdd(double *address, double val) {
11
+ unsigned long long int* address_as_ull = (unsigned long long int*)address;
12
+ unsigned long long int old = *address_as_ull, assumed;
13
+ if (val == 0.0)
14
+ return __longlong_as_double(old);
15
+ do {
16
+ assumed = old;
17
+ old = atomicCAS(address_as_ull, assumed, __double_as_longlong(val +__longlong_as_double(assumed)));
18
+ } while (assumed != old);
19
+ return __longlong_as_double(old);
20
+ }
21
+ #endif
22
+
23
+ #ifndef WIN32
24
+ template <typename T0, typename T1>
25
+ DEVICE
26
+ inline T0 atomic_add_(T0 &target, T1 source) {
27
+ #ifdef __CUDA_ARCH__
28
+ return atomicAdd(&target, (T0)source);
29
+ #else
30
+ T0 old_val;
31
+ T0 new_val;
32
+ do {
33
+ old_val = target;
34
+ new_val = old_val + source;
35
+ } while (!__atomic_compare_exchange(&target, &old_val, &new_val, true,
36
+ std::memory_order::memory_order_seq_cst,
37
+ std::memory_order::memory_order_seq_cst));
38
+ return old_val;
39
+ #endif
40
+ }
41
+
42
+ DEVICE
43
+ inline
44
+ float atomic_add(float &target, float source) {
45
+ return atomic_add_(target, source);
46
+ }
47
+ DEVICE
48
+ inline
49
+ double atomic_add(double &target, double source) {
50
+ return atomic_add_(target, source);
51
+ }
52
+ #else
53
+ float win_atomic_add(float &target, float source);
54
+ double win_atomic_add(double &target, double source);
55
+ DEVICE
56
+ static float atomic_add(float &target, float source) {
57
+ #ifdef __CUDA_ARCH__
58
+ return atomicAdd(&target, source);
59
+ #else
60
+ return win_atomic_add(target, source);
61
+ #endif
62
+ }
63
+ DEVICE
64
+ static double atomic_add(double &target, double source) {
65
+ #ifdef __CUDA_ARCH__
66
+ return atomicAdd(&target, (double)source);
67
+ #else
68
+ return win_atomic_add(target, source);
69
+ #endif
70
+ }
71
+ #endif
72
+
73
+ template <typename T0, typename T1>
74
+ DEVICE
75
+ inline T0 atomic_add(T0 *target, T1 source) {
76
+ return atomic_add(*target, (T0)source);
77
+ }
78
+
79
+ template <typename T0, typename T1>
80
+ DEVICE
81
+ inline TVector2<T0> atomic_add(TVector2<T0> &target, const TVector2<T1> &source) {
82
+ atomic_add(target[0], source[0]);
83
+ atomic_add(target[1], source[1]);
84
+ return target;
85
+ }
86
+
87
+ template <typename T0, typename T1>
88
+ DEVICE
89
+ inline void atomic_add(T0 *target, const TVector2<T1> &source) {
90
+ atomic_add(target[0], (T0)source[0]);
91
+ atomic_add(target[1], (T0)source[1]);
92
+ }
93
+
94
+ template <typename T0, typename T1>
95
+ DEVICE
96
+ inline TVector3<T0> atomic_add(TVector3<T0> &target, const TVector3<T1> &source) {
97
+ atomic_add(target[0], source[0]);
98
+ atomic_add(target[1], source[1]);
99
+ atomic_add(target[2], source[2]);
100
+ return target;
101
+ }
102
+
103
+ template <typename T0, typename T1>
104
+ DEVICE
105
+ inline void atomic_add(T0 *target, const TVector3<T1> &source) {
106
+ atomic_add(target[0], (T0)source[0]);
107
+ atomic_add(target[1], (T0)source[1]);
108
+ atomic_add(target[2], (T0)source[2]);
109
+ }
110
+
111
+ template <typename T0, typename T1>
112
+ DEVICE
113
+ inline TVector4<T0> atomic_add(TVector4<T0> &target, const TVector4<T1> &source) {
114
+ atomic_add(target[0], source[0]);
115
+ atomic_add(target[1], source[1]);
116
+ atomic_add(target[2], source[2]);
117
+ atomic_add(target[3], source[3]);
118
+ return target;
119
+ }
120
+
121
+ template <typename T0, typename T1>
122
+ DEVICE
123
+ inline void atomic_add(T0 *target, const TVector4<T1> &source) {
124
+ atomic_add(target[0], (T0)source[0]);
125
+ atomic_add(target[1], (T0)source[1]);
126
+ atomic_add(target[2], (T0)source[2]);
127
+ atomic_add(target[3], (T0)source[3]);
128
+ }
129
+
130
+ template <typename T0, typename T1>
131
+ DEVICE
132
+ inline void atomic_add(T0 *target, const TMatrix3x3<T1> &source) {
133
+ for (int i = 0; i < 3; i++) {
134
+ for (int j = 0; j < 3; j++) {
135
+ atomic_add(target[3 * i + j], (T0)source(i, j));
136
+ }
137
+ }
138
+ }
139
+
cdf.h ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #pragma once
2
+
3
+ #include "diffvg.h"
4
+
5
+ DEVICE int sample(const float *cdf, int num_entries, float u, float *updated_u = nullptr) {
6
+ // Binary search the cdf
7
+ auto lb = 0;
8
+ auto len = num_entries - 1 - lb;
9
+ while (len > 0) {
10
+ auto half_len = len / 2;
11
+ auto mid = lb + half_len;
12
+ assert(mid >= 0 && mid < num_entries);
13
+ if (u < cdf[mid]) {
14
+ len = half_len;
15
+ } else {
16
+ lb = mid + 1;
17
+ len = len - half_len - 1;
18
+ }
19
+ }
20
+ lb = clamp(lb, 0, num_entries - 1);
21
+ if (updated_u != nullptr) {
22
+ if (lb > 0) {
23
+ *updated_u = (u - cdf[lb - 1]) / (cdf[lb] - cdf[lb - 1]);
24
+ } else {
25
+ *updated_u = u / cdf[lb];
26
+ }
27
+ }
28
+ return lb;
29
+ }
cls_name/cls_name.csv ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ 自行车
3
+ 汽车
4
+ 摩托车
5
+ 飞机
6
+ 公交车
7
+ 火车
8
+ 卡车
9
+
10
+ 红绿灯
11
+ 消防栓
12
+ 停止标志
13
+ 停车收费表
14
+ 长凳
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+
23
+ 斑马
24
+ 长颈鹿
25
+ 背包
26
+ 雨伞
27
+ 手提包
28
+ 领带
29
+ 手提箱
30
+ 飞盘
31
+ 滑雪板
32
+ 单板滑雪
33
+ 运动球
34
+ 风筝
35
+ 棒球棒
36
+ 棒球手套
37
+ 滑板
38
+ 冲浪板
39
+ 网球拍
40
+ 瓶子
41
+ 红酒杯
42
+ 杯子
43
+ 叉子
44
+
45
+
46
+
47
+ 香蕉
48
+ 苹果
49
+ 三明治
50
+ 橙子
51
+ 西兰花
52
+ 胡萝卜
53
+ 热狗
54
+ 比萨
55
+ 甜甜圈
56
+ 蛋糕
57
+ 椅子
58
+ 长椅
59
+ 盆栽
60
+
61
+ 餐桌
62
+ 马桶
63
+ 电视
64
+ 笔记本电脑
65
+ 鼠标
66
+ 遥控器
67
+ 键盘
68
+ 手机
69
+ 微波炉
70
+ 烤箱
71
+ 烤面包机
72
+ 洗碗槽
73
+ 冰箱
74
+
75
+ 时钟
76
+ 花瓶
77
+ 剪刀
78
+ 泰迪熊
79
+ 吹风机
80
+ 牙刷
cls_name/cls_name.yaml ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ model_cls_name: ['人', '自行车', '汽车', '摩托车', '飞机', '公交车', '火车', '卡车', '船', '红绿灯', '消防栓', '停止标志',
2
+ '停车收费表', '长凳', '鸟', '猫', '狗', '马', '羊', '牛', '象', '熊', '斑马', '长颈鹿', '背包', '雨伞', '手提包', '领带',
3
+ '手提箱', '飞盘', '滑雪板', '单板滑雪', '运动球', '风筝', '棒球棒', '棒球手套', '滑板', '冲浪板', '网球拍', '瓶子', '红酒杯',
4
+ '杯子', '叉子', '刀', '勺', '碗', '香蕉', '苹果', '三明治', '橙子', '西兰花', '胡萝卜', '热狗', '比萨', '甜甜圈', '蛋糕',
5
+ '椅子', '长椅', '盆栽', '床', '餐桌', '马桶', '电视', '笔记本电脑', '鼠标', '遥控器', '键盘', '手机', '微波炉', '烤箱',
6
+ '烤面包机', '洗碗槽', '冰箱', '书', '时钟', '花瓶', '剪刀', '泰迪熊', '吹风机', '牙刷'
7
+ ]
cmake/FindTensorFlow.cmake ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # https://github.com/PatWie/tensorflow-cmake/blob/master/cmake/modules/FindTensorFlow.cmake
2
+
3
+ execute_process(
4
+ COMMAND python -c "exec(\"try:\\n import tensorflow as tf; print(tf.__version__); print(tf.__cxx11_abi_flag__);print(tf.sysconfig.get_include()); print(tf.sysconfig.get_lib())\\nexcept ImportError:\\n exit(1)\")"
5
+ OUTPUT_VARIABLE TF_INFORMATION_STRING
6
+ OUTPUT_STRIP_TRAILING_WHITESPACE
7
+ RESULT_VARIABLE retcode)
8
+
9
+ if("${retcode}" STREQUAL "0")
10
+ string(REPLACE "\n" ";" TF_INFORMATION_LIST ${TF_INFORMATION_STRING})
11
+ list(GET TF_INFORMATION_LIST 0 TF_DETECTED_VERSION)
12
+ list(GET TF_INFORMATION_LIST 1 TF_DETECTED_ABI)
13
+ list(GET TF_INFORMATION_LIST 2 TF_DETECTED_INCLUDE_DIR)
14
+ list(GET TF_INFORMATION_LIST 3 TF_DETECTED_LIBRARY_DIR)
15
+ if(WIN32)
16
+ find_library(TF_DETECTED_LIBRARY NAMES _pywrap_tensorflow_internal PATHS
17
+ ${TF_DETECTED_LIBRARY_DIR}/python)
18
+ else()
19
+ # For some reason my tensorflow doesn't have a .so file
20
+ list(APPEND CMAKE_FIND_LIBRARY_SUFFIXES .so.1)
21
+ list(APPEND CMAKE_FIND_LIBRARY_SUFFIXES .so.2)
22
+ find_library(TF_DETECTED_LIBRARY NAMES tensorflow_framework PATHS
23
+ ${TF_DETECTED_LIBRARY_DIR})
24
+ endif()
25
+ set(TensorFlow_VERSION ${TF_DETECTED_VERSION})
26
+ set(TensorFlow_ABI ${TF_DETECTED_ABI})
27
+ set(TensorFlow_INCLUDE_DIR ${TF_DETECTED_INCLUDE_DIR})
28
+ set(TensorFlow_LIBRARY ${TF_DETECTED_LIBRARY})
29
+ if(TensorFlow_LIBRARY AND TensorFlow_INCLUDE_DIR)
30
+ set(TensorFlow_FOUND TRUE)
31
+ else()
32
+ set(TensorFlow_FOUND FALSE)
33
+ endif()
34
+ endif()
cmake/FindThrust.cmake ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ##=============================================================================
2
+ ##
3
+ ## Copyright (c) Kitware, Inc.
4
+ ## All rights reserved.
5
+ ## See LICENSE.txt for details.
6
+ ##
7
+ ## This software is distributed WITHOUT ANY WARRANTY; without even
8
+ ## the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
9
+ ## PURPOSE. See the above copyright notice for more information.
10
+ ##
11
+ ## Copyright 2012 Sandia Corporation.
12
+ ## Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
13
+ ## the U.S. Government retains certain rights in this software.
14
+ ##
15
+ ##=============================================================================
16
+
17
+ #
18
+ # FindThrust
19
+ #
20
+ # This module finds the Thrust header files and extrats their version. It
21
+ # sets the following variables.
22
+ #
23
+ # THRUST_INCLUDE_DIR - Include directory for thrust header files. (All header
24
+ # files will actually be in the thrust subdirectory.)
25
+ # THRUST_VERSION - Version of thrust in the form "major.minor.patch".
26
+ #
27
+
28
+ find_path(THRUST_INCLUDE_DIR
29
+ HINTS /usr/include/cuda
30
+ /usr/local/include
31
+ /usr/local/cuda/include
32
+ ${CUDA_INCLUDE_DIRS}
33
+ ./thrust
34
+ ../thrust
35
+ NAMES thrust/version.h
36
+ )
37
+
38
+ if (THRUST_INCLUDE_DIR)
39
+ set(THRUST_FOUND TRUE)
40
+ endif ()
color.cpp ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #include "color.h"
2
+
3
+ void LinearGradient::copy_to(ptr<float> stop_offsets,
4
+ ptr<float> stop_colors) const {
5
+ float *o = stop_offsets.get();
6
+ float *c = stop_colors.get();
7
+ for (int i = 0; i < num_stops; i++) {
8
+ o[i] = this->stop_offsets[i];
9
+ }
10
+ for (int i = 0; i < 4 * num_stops; i++) {
11
+ c[i] = this->stop_colors[i];
12
+ }
13
+ }
14
+
15
+ void RadialGradient::copy_to(ptr<float> stop_offsets,
16
+ ptr<float> stop_colors) const {
17
+ float *o = stop_offsets.get();
18
+ float *c = stop_colors.get();
19
+ for (int i = 0; i < num_stops; i++) {
20
+ o[i] = this->stop_offsets[i];
21
+ }
22
+ for (int i = 0; i < 4 * num_stops; i++) {
23
+ c[i] = this->stop_colors[i];
24
+ }
25
+ }
color.h ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #pragma once
2
+
3
+ #include "diffvg.h"
4
+ #include "vector.h"
5
+ #include "ptr.h"
6
+
7
+ enum class ColorType {
8
+ Constant,
9
+ LinearGradient,
10
+ RadialGradient
11
+ };
12
+
13
+ struct Constant {
14
+ Vector4f color;
15
+
16
+ ptr<void> get_ptr() {
17
+ return ptr<void>(this);
18
+ }
19
+ };
20
+
21
+ struct LinearGradient {
22
+ LinearGradient(const Vector2f &begin,
23
+ const Vector2f &end,
24
+ int num_stops,
25
+ ptr<float> stop_offsets,
26
+ ptr<float> stop_colors)
27
+ : begin(begin), end(end), num_stops(num_stops),
28
+ stop_offsets(stop_offsets.get()), stop_colors(stop_colors.get()) {}
29
+
30
+ ptr<void> get_ptr() {
31
+ return ptr<void>(this);
32
+ }
33
+
34
+ void copy_to(ptr<float> stop_offset,
35
+ ptr<float> stop_colors) const;
36
+
37
+ Vector2f begin, end;
38
+ int num_stops;
39
+ float *stop_offsets;
40
+ float *stop_colors; // rgba
41
+ };
42
+
43
+ struct RadialGradient {
44
+ RadialGradient(const Vector2f &center,
45
+ const Vector2f &radius,
46
+ int num_stops,
47
+ ptr<float> stop_offsets,
48
+ ptr<float> stop_colors)
49
+ : center(center), radius(radius), num_stops(num_stops),
50
+ stop_offsets(stop_offsets.get()), stop_colors(stop_colors.get()) {}
51
+
52
+ ptr<void> get_ptr() {
53
+ return ptr<void>(this);
54
+ }
55
+
56
+ void copy_to(ptr<float> stop_offset,
57
+ ptr<float> stop_colors) const;
58
+
59
+ Vector2f center, radius;
60
+ int num_stops;
61
+ float *stop_offsets;
62
+ float *stop_colors; // rgba
63
+ };
compute_distance.h ADDED
@@ -0,0 +1,949 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #pragma once
2
+
3
+ #include "diffvg.h"
4
+ #include "edge_query.h"
5
+ #include "scene.h"
6
+ #include "shape.h"
7
+ #include "solve.h"
8
+ #include "vector.h"
9
+
10
+ #include <cassert>
11
+
12
+ struct ClosestPointPathInfo {
13
+ int base_point_id;
14
+ int point_id;
15
+ float t_root;
16
+ };
17
+
18
+ DEVICE
19
+ inline
20
+ bool closest_point(const Circle &circle, const Vector2f &pt,
21
+ Vector2f *result) {
22
+ *result = circle.center + circle.radius * normalize(pt - circle.center);
23
+ return false;
24
+ }
25
+
26
+ DEVICE
27
+ inline
28
+ bool closest_point(const Path &path, const BVHNode *bvh_nodes, const Vector2f &pt, float max_radius,
29
+ ClosestPointPathInfo *path_info,
30
+ Vector2f *result) {
31
+ auto min_dist = max_radius;
32
+ auto ret_pt = Vector2f{0, 0};
33
+ auto found = false;
34
+ auto num_segments = path.num_base_points;
35
+ constexpr auto max_bvh_size = 128;
36
+ int bvh_stack[max_bvh_size];
37
+ auto stack_size = 0;
38
+ bvh_stack[stack_size++] = 2 * num_segments - 2;
39
+ while (stack_size > 0) {
40
+ const BVHNode &node = bvh_nodes[bvh_stack[--stack_size]];
41
+ if (node.child1 < 0) {
42
+ // leaf
43
+ auto base_point_id = node.child0;
44
+ auto point_id = - node.child1 - 1;
45
+ assert(base_point_id < num_segments);
46
+ assert(point_id < path.num_points);
47
+ auto dist = 0.f;
48
+ auto closest_pt = Vector2f{0, 0};
49
+ auto t_root = 0.f;
50
+ if (path.num_control_points[base_point_id] == 0) {
51
+ // Straight line
52
+ auto i0 = point_id;
53
+ auto i1 = (point_id + 1) % path.num_points;
54
+ auto p0 = Vector2f{path.points[2 * i0], path.points[2 * i0 + 1]};
55
+ auto p1 = Vector2f{path.points[2 * i1], path.points[2 * i1 + 1]};
56
+ // project pt to line
57
+ auto t = dot(pt - p0, p1 - p0) / dot(p1 - p0, p1 - p0);
58
+ if (t < 0) {
59
+ dist = distance(p0, pt);
60
+ closest_pt = p0;
61
+ t_root = 0;
62
+ } else if (t > 1) {
63
+ dist = distance(p1, pt);
64
+ closest_pt = p1;
65
+ t_root = 1;
66
+ } else {
67
+ dist = distance(p0 + t * (p1 - p0), pt);
68
+ closest_pt = p0 + t * (p1 - p0);
69
+ t_root = t;
70
+ }
71
+ } else if (path.num_control_points[base_point_id] == 1) {
72
+ // Quadratic Bezier curve
73
+ auto i0 = point_id;
74
+ auto i1 = point_id + 1;
75
+ auto i2 = (point_id + 2) % path.num_points;
76
+ auto p0 = Vector2f{path.points[2 * i0], path.points[2 * i0 + 1]};
77
+ auto p1 = Vector2f{path.points[2 * i1], path.points[2 * i1 + 1]};
78
+ auto p2 = Vector2f{path.points[2 * i2], path.points[2 * i2 + 1]};
79
+ if (path.use_distance_approx) {
80
+ closest_pt = quadratic_closest_pt_approx(p0, p1, p2, pt, &t_root);
81
+ dist = distance(closest_pt, pt);
82
+ } else {
83
+ auto eval = [&](float t) -> Vector2f {
84
+ auto tt = 1 - t;
85
+ return (tt*tt)*p0 + (2*tt*t)*p1 + (t*t)*p2;
86
+ };
87
+ auto pt0 = eval(0);
88
+ auto pt1 = eval(1);
89
+ auto dist0 = distance(pt0, pt);
90
+ auto dist1 = distance(pt1, pt);
91
+ {
92
+ dist = dist0;
93
+ closest_pt = pt0;
94
+ t_root = 0;
95
+ }
96
+ if (dist1 < dist) {
97
+ dist = dist1;
98
+ closest_pt = pt1;
99
+ t_root = 1;
100
+ }
101
+ // The curve is (1-t)^2p0 + 2(1-t)tp1 + t^2p2
102
+ // = (p0-2p1+p2)t^2+(-2p0+2p1)t+p0 = q
103
+ // Want to solve (q - pt) dot q' = 0
104
+ // q' = (p0-2p1+p2)t + (-p0+p1)
105
+ // Expanding (p0-2p1+p2)^2 t^3 +
106
+ // 3(p0-2p1+p2)(-p0+p1) t^2 +
107
+ // (2(-p0+p1)^2+(p0-2p1+p2)(p0-pt))t +
108
+ // (-p0+p1)(p0-pt) = 0
109
+ auto A = sum((p0-2*p1+p2)*(p0-2*p1+p2));
110
+ auto B = sum(3*(p0-2*p1+p2)*(-p0+p1));
111
+ auto C = sum(2*(-p0+p1)*(-p0+p1)+(p0-2*p1+p2)*(p0-pt));
112
+ auto D = sum((-p0+p1)*(p0-pt));
113
+ float t[3];
114
+ int num_sol = solve_cubic(A, B, C, D, t);
115
+ for (int j = 0; j < num_sol; j++) {
116
+ if (t[j] >= 0 && t[j] <= 1) {
117
+ auto p = eval(t[j]);
118
+ auto distp = distance(p, pt);
119
+ if (distp < dist) {
120
+ dist = distp;
121
+ closest_pt = p;
122
+ t_root = t[j];
123
+ }
124
+ }
125
+ }
126
+ }
127
+ } else if (path.num_control_points[base_point_id] == 2) {
128
+ // Cubic Bezier curve
129
+ auto i0 = point_id;
130
+ auto i1 = point_id + 1;
131
+ auto i2 = point_id + 2;
132
+ auto i3 = (point_id + 3) % path.num_points;
133
+ auto p0 = Vector2f{path.points[2 * i0], path.points[2 * i0 + 1]};
134
+ auto p1 = Vector2f{path.points[2 * i1], path.points[2 * i1 + 1]};
135
+ auto p2 = Vector2f{path.points[2 * i2], path.points[2 * i2 + 1]};
136
+ auto p3 = Vector2f{path.points[2 * i3], path.points[2 * i3 + 1]};
137
+ auto eval = [&](float t) -> Vector2f {
138
+ auto tt = 1 - t;
139
+ return (tt*tt*tt)*p0 + (3*tt*tt*t)*p1 + (3*tt*t*t)*p2 + (t*t*t)*p3;
140
+ };
141
+ auto pt0 = eval(0);
142
+ auto pt1 = eval(1);
143
+ auto dist0 = distance(pt0, pt);
144
+ auto dist1 = distance(pt1, pt);
145
+ {
146
+ dist = dist0;
147
+ closest_pt = pt0;
148
+ t_root = 0;
149
+ }
150
+ if (dist1 < dist) {
151
+ dist = dist1;
152
+ closest_pt = pt1;
153
+ t_root = 1;
154
+ }
155
+ // The curve is (1 - t)^3 p0 + 3 * (1 - t)^2 t p1 + 3 * (1 - t) t^2 p2 + t^3 p3
156
+ // = (-p0+3p1-3p2+p3) t^3 + (3p0-6p1+3p2) t^2 + (-3p0+3p1) t + p0
157
+ // Want to solve (q - pt) dot q' = 0
158
+ // q' = 3*(-p0+3p1-3p2+p3)t^2 + 2*(3p0-6p1+3p2)t + (-3p0+3p1)
159
+ // Expanding
160
+ // 3*(-p0+3p1-3p2+p3)^2 t^5
161
+ // 5*(-p0+3p1-3p2+p3)(3p0-6p1+3p2) t^4
162
+ // 4*(-p0+3p1-3p2+p3)(-3p0+3p1) + 2*(3p0-6p1+3p2)^2 t^3
163
+ // 3*(3p0-6p1+3p2)(-3p0+3p1) + 3*(-p0+3p1-3p2+p3)(p0-pt) t^2
164
+ // (-3p0+3p1)^2+2(p0-pt)(3p0-6p1+3p2) t
165
+ // (p0-pt)(-3p0+3p1)
166
+ double A = 3*sum((-p0+3*p1-3*p2+p3)*(-p0+3*p1-3*p2+p3));
167
+ double B = 5*sum((-p0+3*p1-3*p2+p3)*(3*p0-6*p1+3*p2));
168
+ double C = 4*sum((-p0+3*p1-3*p2+p3)*(-3*p0+3*p1)) + 2*sum((3*p0-6*p1+3*p2)*(3*p0-6*p1+3*p2));
169
+ double D = 3*(sum((3*p0-6*p1+3*p2)*(-3*p0+3*p1)) + sum((-p0+3*p1-3*p2+p3)*(p0-pt)));
170
+ double E = sum((-3*p0+3*p1)*(-3*p0+3*p1)) + 2*sum((p0-pt)*(3*p0-6*p1+3*p2));
171
+ double F = sum((p0-pt)*(-3*p0+3*p1));
172
+ // normalize the polynomial
173
+ B /= A;
174
+ C /= A;
175
+ D /= A;
176
+ E /= A;
177
+ F /= A;
178
+ // Isolator Polynomials:
179
+ // https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.133.2233&rep=rep1&type=pdf
180
+ // x/5 + B/25
181
+ // /-----------------------------------------------------
182
+ // 5x^4 + 4B x^3 + 3C x^2 + 2D x + E / x^5 + B x^4 + C x^3 + D x^2 + E x + F
183
+ // x^5 + 4B/5 x^4 + 3C/5 x^3 + 2D/5 x^2 + E/5 x
184
+ // ----------------------------------------------------
185
+ // B/5 x^4 + 2C/5 x^3 + 3D/5 x^2 + 4E/5 x + F
186
+ // B/5 x^4 + 4B^2/25 x^3 + 3BC/25 x^2 + 2BD/25 x + BE/25
187
+ // ----------------------------------------------------
188
+ // (2C/5 - 4B^2/25)x^3 + (3D/5-3BC/25)x^2 + (4E/5-2BD/25) + (F-BE/25)
189
+ auto p1A = ((2 / 5.f) * C - (4 / 25.f) * B * B);
190
+ auto p1B = ((3 / 5.f) * D - (3 / 25.f) * B * C);
191
+ auto p1C = ((4 / 5.f) * E - (2 / 25.f) * B * D);
192
+ auto p1D = F - B * E / 25.f;
193
+ // auto q1A = 1 / 5.f;
194
+ // auto q1B = B / 25.f;
195
+ // x/5 + B/25 = 0
196
+ // x = -B/5
197
+ auto q_root = -B/5.f;
198
+ double p_roots[3];
199
+ int num_sol = solve_cubic(p1A, p1B, p1C, p1D, p_roots);
200
+ float intervals[4];
201
+ if (q_root >= 0 && q_root <= 1) {
202
+ intervals[0] = q_root;
203
+ }
204
+ for (int j = 0; j < num_sol; j++) {
205
+ intervals[j + 1] = p_roots[j];
206
+ }
207
+ auto num_intervals = 1 + num_sol;
208
+ // sort intervals
209
+ for (int j = 1; j < num_intervals; j++) {
210
+ for (int k = j; k > 0 && intervals[k - 1] > intervals[k]; k--) {
211
+ auto tmp = intervals[k];
212
+ intervals[k] = intervals[k - 1];
213
+ intervals[k - 1] = tmp;
214
+ }
215
+ }
216
+ auto eval_polynomial = [&] (double t) {
217
+ return t*t*t*t*t+
218
+ B*t*t*t*t+
219
+ C*t*t*t+
220
+ D*t*t+
221
+ E*t+
222
+ F;
223
+ };
224
+ auto eval_polynomial_deriv = [&] (double t) {
225
+ return 5*t*t*t*t+
226
+ 4*B*t*t*t+
227
+ 3*C*t*t+
228
+ 2*D*t+
229
+ E;
230
+ };
231
+ auto lower_bound = 0.f;
232
+ for (int j = 0; j < num_intervals + 1; j++) {
233
+ if (j < num_intervals && intervals[j] < 0.f) {
234
+ continue;
235
+ }
236
+ auto upper_bound = j < num_intervals ?
237
+ min(intervals[j], 1.f) : 1.f;
238
+ auto lb = lower_bound;
239
+ auto ub = upper_bound;
240
+ auto lb_eval = eval_polynomial(lb);
241
+ auto ub_eval = eval_polynomial(ub);
242
+ if (lb_eval * ub_eval > 0) {
243
+ // Doesn't have root
244
+ continue;
245
+ }
246
+ if (lb_eval > ub_eval) {
247
+ swap_(lb, ub);
248
+ }
249
+ auto t = 0.5f * (lb + ub);
250
+ auto num_iter = 20;
251
+ for (int it = 0; it < num_iter; it++) {
252
+ if (!(t >= lb && t <= ub)) {
253
+ t = 0.5f * (lb + ub);
254
+ }
255
+ auto value = eval_polynomial(t);
256
+ if (fabs(value) < 1e-5f || it == num_iter - 1) {
257
+ break;
258
+ }
259
+ // The derivative may not be entirely accurate,
260
+ // but the bisection is going to handle this
261
+ if (value > 0.f) {
262
+ ub = t;
263
+ } else {
264
+ lb = t;
265
+ }
266
+ auto derivative = eval_polynomial_deriv(t);
267
+ t -= value / derivative;
268
+ }
269
+ auto p = eval(t);
270
+ auto distp = distance(p, pt);
271
+ if (distp < dist) {
272
+ dist = distp;
273
+ closest_pt = p;
274
+ t_root = t;
275
+ }
276
+ if (upper_bound >= 1.f) {
277
+ break;
278
+ }
279
+ lower_bound = upper_bound;
280
+ }
281
+ } else {
282
+ assert(false);
283
+ }
284
+ if (dist < min_dist) {
285
+ min_dist = dist;
286
+ ret_pt = closest_pt;
287
+ path_info->base_point_id = base_point_id;
288
+ path_info->point_id = point_id;
289
+ path_info->t_root = t_root;
290
+ found = true;
291
+ }
292
+ } else {
293
+ assert(node.child0 >= 0 && node.child1 >= 0);
294
+ const AABB &b0 = bvh_nodes[node.child0].box;
295
+ if (within_distance(b0, pt, min_dist)) {
296
+ bvh_stack[stack_size++] = node.child0;
297
+ }
298
+ const AABB &b1 = bvh_nodes[node.child1].box;
299
+ if (within_distance(b1, pt, min_dist)) {
300
+ bvh_stack[stack_size++] = node.child1;
301
+ }
302
+ assert(stack_size <= max_bvh_size);
303
+ }
304
+ }
305
+ if (found) {
306
+ assert(path_info->base_point_id < num_segments);
307
+ }
308
+ *result = ret_pt;
309
+ return found;
310
+ }
311
+
312
+ DEVICE
313
+ inline
314
+ bool closest_point(const Rect &rect, const Vector2f &pt,
315
+ Vector2f *result) {
316
+ auto min_dist = 0.f;
317
+ auto closest_pt = Vector2f{0, 0};
318
+ auto update = [&](const Vector2f &p0, const Vector2f &p1, bool first) {
319
+ // project pt to line
320
+ auto t = dot(pt - p0, p1 - p0) / dot(p1 - p0, p1 - p0);
321
+ if (t < 0) {
322
+ auto d = distance(p0, pt);
323
+ if (first || d < min_dist) {
324
+ min_dist = d;
325
+ closest_pt = p0;
326
+ }
327
+ } else if (t > 1) {
328
+ auto d = distance(p1, pt);
329
+ if (first || d < min_dist) {
330
+ min_dist = d;
331
+ closest_pt = p1;
332
+ }
333
+ } else {
334
+ auto p = p0 + t * (p1 - p0);
335
+ auto d = distance(p, pt);
336
+ if (first || d < min_dist) {
337
+ min_dist = d;
338
+ closest_pt = p0;
339
+ }
340
+ }
341
+ };
342
+ auto left_top = rect.p_min;
343
+ auto right_top = Vector2f{rect.p_max.x, rect.p_min.y};
344
+ auto left_bottom = Vector2f{rect.p_min.x, rect.p_max.y};
345
+ auto right_bottom = rect.p_max;
346
+ update(left_top, left_bottom, true);
347
+ update(left_top, right_top, false);
348
+ update(right_top, right_bottom, false);
349
+ update(left_bottom, right_bottom, false);
350
+ *result = closest_pt;
351
+ return true;
352
+ }
353
+
354
+ DEVICE
355
+ inline
356
+ bool closest_point(const Shape &shape, const BVHNode *bvh_nodes, const Vector2f &pt, float max_radius,
357
+ ClosestPointPathInfo *path_info,
358
+ Vector2f *result) {
359
+ switch (shape.type) {
360
+ case ShapeType::Circle:
361
+ return closest_point(*(const Circle *)shape.ptr, pt, result);
362
+ case ShapeType::Ellipse:
363
+ // https://www.geometrictools.com/Documentation/DistancePointEllipseEllipsoid.pdf
364
+ assert(false);
365
+ return false;
366
+ case ShapeType::Path:
367
+ return closest_point(*(const Path *)shape.ptr, bvh_nodes, pt, max_radius, path_info, result);
368
+ case ShapeType::Rect:
369
+ return closest_point(*(const Rect *)shape.ptr, pt, result);
370
+ }
371
+ assert(false);
372
+ return false;
373
+ }
374
+
375
+ DEVICE
376
+ inline
377
+ bool compute_distance(const SceneData &scene,
378
+ int shape_group_id,
379
+ const Vector2f &pt,
380
+ float max_radius,
381
+ int *min_shape_id,
382
+ Vector2f *closest_pt_,
383
+ ClosestPointPathInfo *path_info,
384
+ float *result) {
385
+ const ShapeGroup &shape_group = scene.shape_groups[shape_group_id];
386
+ // pt is in canvas space, transform it to shape's local space
387
+ auto local_pt = xform_pt(shape_group.canvas_to_shape, pt);
388
+
389
+ constexpr auto max_bvh_stack_size = 64;
390
+ int bvh_stack[max_bvh_stack_size];
391
+ auto stack_size = 0;
392
+ bvh_stack[stack_size++] = 2 * shape_group.num_shapes - 2;
393
+ const auto &bvh_nodes = scene.shape_groups_bvh_nodes[shape_group_id];
394
+
395
+ auto min_dist = max_radius;
396
+ auto found = false;
397
+
398
+ while (stack_size > 0) {
399
+ const BVHNode &node = bvh_nodes[bvh_stack[--stack_size]];
400
+ if (node.child1 < 0) {
401
+ // leaf
402
+ auto shape_id = node.child0;
403
+ const auto &shape = scene.shapes[shape_id];
404
+ ClosestPointPathInfo local_path_info{-1, -1};
405
+ auto local_closest_pt = Vector2f{0, 0};
406
+ if (closest_point(shape, scene.path_bvhs[shape_id], local_pt, max_radius, &local_path_info, &local_closest_pt)) {
407
+ auto closest_pt = xform_pt(shape_group.shape_to_canvas, local_closest_pt);
408
+ auto dist = distance(closest_pt, pt);
409
+ if (!found || dist < min_dist) {
410
+ found = true;
411
+ min_dist = dist;
412
+ if (min_shape_id != nullptr) {
413
+ *min_shape_id = shape_id;
414
+ }
415
+ if (closest_pt_ != nullptr) {
416
+ *closest_pt_ = closest_pt;
417
+ }
418
+ if (path_info != nullptr) {
419
+ *path_info = local_path_info;
420
+ }
421
+ }
422
+ }
423
+ } else {
424
+ assert(node.child0 >= 0 && node.child1 >= 0);
425
+ const AABB &b0 = bvh_nodes[node.child0].box;
426
+ if (inside(b0, local_pt, max_radius)) {
427
+ bvh_stack[stack_size++] = node.child0;
428
+ }
429
+ const AABB &b1 = bvh_nodes[node.child1].box;
430
+ if (inside(b1, local_pt, max_radius)) {
431
+ bvh_stack[stack_size++] = node.child1;
432
+ }
433
+ assert(stack_size <= max_bvh_stack_size);
434
+ }
435
+ }
436
+
437
+ *result = min_dist;
438
+ return found;
439
+ }
440
+
441
+
442
+ DEVICE
443
+ inline
444
+ void d_closest_point(const Circle &circle,
445
+ const Vector2f &pt,
446
+ const Vector2f &d_closest_pt,
447
+ Circle &d_circle,
448
+ Vector2f &d_pt) {
449
+ // return circle.center + circle.radius * normalize(pt - circle.center);
450
+ auto d_center = d_closest_pt *
451
+ (1 + d_normalize(pt - circle.center, circle.radius * d_closest_pt));
452
+ atomic_add(&d_circle.center.x, d_center);
453
+ atomic_add(&d_circle.radius, dot(d_closest_pt, normalize(pt - circle.center)));
454
+ }
455
+
456
+ DEVICE
457
+ inline
458
+ void d_closest_point(const Path &path,
459
+ const Vector2f &pt,
460
+ const Vector2f &d_closest_pt,
461
+ const ClosestPointPathInfo &path_info,
462
+ Path &d_path,
463
+ Vector2f &d_pt) {
464
+ auto base_point_id = path_info.base_point_id;
465
+ auto point_id = path_info.point_id;
466
+ auto min_t_root = path_info.t_root;
467
+
468
+ if (path.num_control_points[base_point_id] == 0) {
469
+ // Straight line
470
+ auto i0 = point_id;
471
+ auto i1 = (point_id + 1) % path.num_points;
472
+ auto p0 = Vector2f{path.points[2 * i0], path.points[2 * i0 + 1]};
473
+ auto p1 = Vector2f{path.points[2 * i1], path.points[2 * i1 + 1]};
474
+ // project pt to line
475
+ auto t = dot(pt - p0, p1 - p0) / dot(p1 - p0, p1 - p0);
476
+ auto d_p0 = Vector2f{0, 0};
477
+ auto d_p1 = Vector2f{0, 0};
478
+ if (t < 0) {
479
+ d_p0 += d_closest_pt;
480
+ } else if (t > 1) {
481
+ d_p1 += d_closest_pt;
482
+ } else {
483
+ auto d_p = d_closest_pt;
484
+ // p = p0 + t * (p1 - p0)
485
+ d_p0 += d_p * (1 - t);
486
+ d_p1 += d_p * t;
487
+ }
488
+ atomic_add(d_path.points + 2 * i0, d_p0);
489
+ atomic_add(d_path.points + 2 * i1, d_p1);
490
+ } else if (path.num_control_points[base_point_id] == 1) {
491
+ // Quadratic Bezier curve
492
+ auto i0 = point_id;
493
+ auto i1 = point_id + 1;
494
+ auto i2 = (point_id + 2) % path.num_points;
495
+ auto p0 = Vector2f{path.points[2 * i0], path.points[2 * i0 + 1]};
496
+ auto p1 = Vector2f{path.points[2 * i1], path.points[2 * i1 + 1]};
497
+ auto p2 = Vector2f{path.points[2 * i2], path.points[2 * i2 + 1]};
498
+ // auto eval = [&](float t) -> Vector2f {
499
+ // auto tt = 1 - t;
500
+ // return (tt*tt)*p0 + (2*tt*t)*p1 + (t*t)*p2;
501
+ // };
502
+ // auto dist0 = distance(eval(0), pt);
503
+ // auto dist1 = distance(eval(1), pt);
504
+ auto d_p0 = Vector2f{0, 0};
505
+ auto d_p1 = Vector2f{0, 0};
506
+ auto d_p2 = Vector2f{0, 0};
507
+ auto t = min_t_root;
508
+ if (t == 0) {
509
+ d_p0 += d_closest_pt;
510
+ } else if (t == 1) {
511
+ d_p2 += d_closest_pt;
512
+ } else {
513
+ // The curve is (1-t)^2p0 + 2(1-t)tp1 + t^2p2
514
+ // = (p0-2p1+p2)t^2+(-2p0+2p1)t+p0 = q
515
+ // Want to solve (q - pt) dot q' = 0
516
+ // q' = (p0-2p1+p2)t + (-p0+p1)
517
+ // Expanding (p0-2p1+p2)^2 t^3 +
518
+ // 3(p0-2p1+p2)(-p0+p1) t^2 +
519
+ // (2(-p0+p1)^2+(p0-2p1+p2)(p0-pt))t +
520
+ // (-p0+p1)(p0-pt) = 0
521
+ auto A = sum((p0-2*p1+p2)*(p0-2*p1+p2));
522
+ auto B = sum(3*(p0-2*p1+p2)*(-p0+p1));
523
+ auto C = sum(2*(-p0+p1)*(-p0+p1)+(p0-2*p1+p2)*(p0-pt));
524
+ // auto D = sum((-p0+p1)*(p0-pt));
525
+ auto d_p = d_closest_pt;
526
+ // p = eval(t)
527
+ auto tt = 1 - t;
528
+ // (tt*tt)*p0 + (2*tt*t)*p1 + (t*t)*p2
529
+ auto d_tt = 2 * tt * dot(d_p, p0) + 2 * t * dot(d_p, p1);
530
+ auto d_t = -d_tt + 2 * tt * dot(d_p, p1) + 2 * t * dot(d_p, p2);
531
+ auto d_p0 = d_p * tt * tt;
532
+ auto d_p1 = 2 * d_p * tt * t;
533
+ auto d_p2 = d_p * t * t;
534
+ // implicit function theorem: dt/dA = -1/(p'(t)) * dp/dA
535
+ auto poly_deriv_t = 3 * A * t * t + 2 * B * t + C;
536
+ if (fabs(poly_deriv_t) > 1e-6f) {
537
+ auto d_A = - (d_t / poly_deriv_t) * t * t * t;
538
+ auto d_B = - (d_t / poly_deriv_t) * t * t;
539
+ auto d_C = - (d_t / poly_deriv_t) * t;
540
+ auto d_D = - (d_t / poly_deriv_t);
541
+ // A = sum((p0-2*p1+p2)*(p0-2*p1+p2))
542
+ // B = sum(3*(p0-2*p1+p2)*(-p0+p1))
543
+ // C = sum(2*(-p0+p1)*(-p0+p1)+(p0-2*p1+p2)*(p0-pt))
544
+ // D = sum((-p0+p1)*(p0-pt))
545
+ d_p0 += 2*d_A*(p0-2*p1+p2)+
546
+ 3*d_B*((-p0+p1)-(p0-2*p1+p2))+
547
+ 2*d_C*(-2*(-p0+p1))+
548
+ d_C*((p0-pt)+(p0-2*p1+p2))+
549
+ 2*d_D*(-(p0-pt)+(-p0+p1));
550
+ d_p1 += (-2)*2*d_A*(p0-2*p1+p2)+
551
+ 3*d_B*(-2*(-p0+p1)+(p0-2*p1+p2))+
552
+ 2*d_C*(2*(-p0+p1))+
553
+ d_C*((-2)*(p0-pt))+
554
+ d_D*(p0-pt);
555
+ d_p2 += 2*d_A*(p0-2*p1+p2)+
556
+ 3*d_B*(-p0+p1)+
557
+ d_C*(p0-pt);
558
+ d_pt += d_C*(-(p0-2*p1+p2))+
559
+ d_D*(-(-p0+p1));
560
+ }
561
+ }
562
+ atomic_add(d_path.points + 2 * i0, d_p0);
563
+ atomic_add(d_path.points + 2 * i1, d_p1);
564
+ atomic_add(d_path.points + 2 * i2, d_p2);
565
+ } else if (path.num_control_points[base_point_id] == 2) {
566
+ // Cubic Bezier curve
567
+ auto i0 = point_id;
568
+ auto i1 = point_id + 1;
569
+ auto i2 = point_id + 2;
570
+ auto i3 = (point_id + 3) % path.num_points;
571
+ auto p0 = Vector2f{path.points[2 * i0], path.points[2 * i0 + 1]};
572
+ auto p1 = Vector2f{path.points[2 * i1], path.points[2 * i1 + 1]};
573
+ auto p2 = Vector2f{path.points[2 * i2], path.points[2 * i2 + 1]};
574
+ auto p3 = Vector2f{path.points[2 * i3], path.points[2 * i3 + 1]};
575
+ // auto eval = [&](float t) -> Vector2f {
576
+ // auto tt = 1 - t;
577
+ // return (tt*tt*tt)*p0 + (3*tt*tt*t)*p1 + (3*tt*t*t)*p2 + (t*t*t)*p3;
578
+ // };
579
+ auto d_p0 = Vector2f{0, 0};
580
+ auto d_p1 = Vector2f{0, 0};
581
+ auto d_p2 = Vector2f{0, 0};
582
+ auto d_p3 = Vector2f{0, 0};
583
+ auto t = min_t_root;
584
+ if (t == 0) {
585
+ // closest_pt = p0
586
+ d_p0 += d_closest_pt;
587
+ } else if (t == 1) {
588
+ // closest_pt = p1
589
+ d_p3 += d_closest_pt;
590
+ } else {
591
+ // The curve is (1 - t)^3 p0 + 3 * (1 - t)^2 t p1 + 3 * (1 - t) t^2 p2 + t^3 p3
592
+ // = (-p0+3p1-3p2+p3) t^3 + (3p0-6p1+3p2) t^2 + (-3p0+3p1) t + p0
593
+ // Want to solve (q - pt) dot q' = 0
594
+ // q' = 3*(-p0+3p1-3p2+p3)t^2 + 2*(3p0-6p1+3p2)t + (-3p0+3p1)
595
+ // Expanding
596
+ // 3*(-p0+3p1-3p2+p3)^2 t^5
597
+ // 5*(-p0+3p1-3p2+p3)(3p0-6p1+3p2) t^4
598
+ // 4*(-p0+3p1-3p2+p3)(-3p0+3p1) + 2*(3p0-6p1+3p2)^2 t^3
599
+ // 3*(3p0-6p1+3p2)(-3p0+3p1) + 3*(-p0+3p1-3p2+p3)(p0-pt) t^2
600
+ // (-3p0+3p1)^2+2(p0-pt)(3p0-6p1+3p2) t
601
+ // (p0-pt)(-3p0+3p1)
602
+ double A = 3*sum((-p0+3*p1-3*p2+p3)*(-p0+3*p1-3*p2+p3));
603
+ double B = 5*sum((-p0+3*p1-3*p2+p3)*(3*p0-6*p1+3*p2));
604
+ double C = 4*sum((-p0+3*p1-3*p2+p3)*(-3*p0+3*p1)) + 2*sum((3*p0-6*p1+3*p2)*(3*p0-6*p1+3*p2));
605
+ double D = 3*(sum((3*p0-6*p1+3*p2)*(-3*p0+3*p1)) + sum((-p0+3*p1-3*p2+p3)*(p0-pt)));
606
+ double E = sum((-3*p0+3*p1)*(-3*p0+3*p1)) + 2*sum((p0-pt)*(3*p0-6*p1+3*p2));
607
+ double F = sum((p0-pt)*(-3*p0+3*p1));
608
+ B /= A;
609
+ C /= A;
610
+ D /= A;
611
+ E /= A;
612
+ F /= A;
613
+ // auto eval_polynomial = [&] (double t) {
614
+ // return t*t*t*t*t+
615
+ // B*t*t*t*t+
616
+ // C*t*t*t+
617
+ // D*t*t+
618
+ // E*t+
619
+ // F;
620
+ // };
621
+ auto eval_polynomial_deriv = [&] (double t) {
622
+ return 5*t*t*t*t+
623
+ 4*B*t*t*t+
624
+ 3*C*t*t+
625
+ 2*D*t+
626
+ E;
627
+ };
628
+
629
+ // auto p = eval(t);
630
+ auto d_p = d_closest_pt;
631
+ // (tt*tt*tt)*p0 + (3*tt*tt*t)*p1 + (3*tt*t*t)*p2 + (t*t*t)*p3
632
+ auto tt = 1 - t;
633
+ auto d_tt = 3 * tt * tt * dot(d_p, p0) +
634
+ 6 * tt * t * dot(d_p, p1) +
635
+ 3 * t * t * dot(d_p, p2);
636
+ auto d_t = -d_tt +
637
+ 3 * tt * tt * dot(d_p, p1) +
638
+ 6 * tt * t * dot(d_p, p2) +
639
+ 3 * t * t * dot(d_p, p3);
640
+ d_p0 += d_p * (tt * tt * tt);
641
+ d_p1 += d_p * (3 * tt * tt * t);
642
+ d_p2 += d_p * (3 * tt * t * t);
643
+ d_p3 += d_p * (t * t * t);
644
+ // implicit function theorem: dt/dA = -1/(p'(t)) * dp/dA
645
+ auto poly_deriv_t = eval_polynomial_deriv(t);
646
+ if (fabs(poly_deriv_t) > 1e-10f) {
647
+ auto d_B = -(d_t / poly_deriv_t) * t * t * t * t;
648
+ auto d_C = -(d_t / poly_deriv_t) * t * t * t;
649
+ auto d_D = -(d_t / poly_deriv_t) * t * t;
650
+ auto d_E = -(d_t / poly_deriv_t) * t;
651
+ auto d_F = -(d_t / poly_deriv_t);
652
+ // B = B' / A
653
+ // C = C' / A
654
+ // D = D' / A
655
+ // E = E' / A
656
+ // F = F' / A
657
+ auto d_A = -d_B * B / A
658
+ -d_C * C / A
659
+ -d_D * D / A
660
+ -d_E * E / A
661
+ -d_F * F / A;
662
+ d_B /= A;
663
+ d_C /= A;
664
+ d_D /= A;
665
+ d_E /= A;
666
+ d_F /= A;
667
+ {
668
+ double A = 3*sum((-p0+3*p1-3*p2+p3)*(-p0+3*p1-3*p2+p3)) + 1e-3;
669
+ double B = 5*sum((-p0+3*p1-3*p2+p3)*(3*p0-6*p1+3*p2));
670
+ double C = 4*sum((-p0+3*p1-3*p2+p3)*(-3*p0+3*p1)) + 2*sum((3*p0-6*p1+3*p2)*(3*p0-6*p1+3*p2));
671
+ double D = 3*(sum((3*p0-6*p1+3*p2)*(-3*p0+3*p1)) + sum((-p0+3*p1-3*p2+p3)*(p0-pt)));
672
+ double E = sum((-3*p0+3*p1)*(-3*p0+3*p1)) + 2*sum((p0-pt)*(3*p0-6*p1+3*p2));
673
+ double F = sum((p0-pt)*(-3*p0+3*p1));
674
+ B /= A;
675
+ C /= A;
676
+ D /= A;
677
+ E /= A;
678
+ F /= A;
679
+ auto eval_polynomial = [&] (double t) {
680
+ return t*t*t*t*t+
681
+ B*t*t*t*t+
682
+ C*t*t*t+
683
+ D*t*t+
684
+ E*t+
685
+ F;
686
+ };
687
+ auto eval_polynomial_deriv = [&] (double t) {
688
+ return 5*t*t*t*t+
689
+ 4*B*t*t*t+
690
+ 3*C*t*t+
691
+ 2*D*t+
692
+ E;
693
+ };
694
+ auto lb = t - 1e-2f;
695
+ auto ub = t + 1e-2f;
696
+ auto lb_eval = eval_polynomial(lb);
697
+ auto ub_eval = eval_polynomial(ub);
698
+ if (lb_eval > ub_eval) {
699
+ swap_(lb, ub);
700
+ }
701
+ auto t_ = 0.5f * (lb + ub);
702
+ auto num_iter = 20;
703
+ for (int it = 0; it < num_iter; it++) {
704
+ if (!(t_ >= lb && t_ <= ub)) {
705
+ t_ = 0.5f * (lb + ub);
706
+ }
707
+ auto value = eval_polynomial(t_);
708
+ if (fabs(value) < 1e-5f || it == num_iter - 1) {
709
+ break;
710
+ }
711
+ // The derivative may not be entirely accurate,
712
+ // but the bisection is going to handle this
713
+ if (value > 0.f) {
714
+ ub = t_;
715
+ } else {
716
+ lb = t_;
717
+ }
718
+ auto derivative = eval_polynomial_deriv(t);
719
+ t_ -= value / derivative;
720
+ }
721
+ }
722
+ // A = 3*sum((-p0+3*p1-3*p2+p3)*(-p0+3*p1-3*p2+p3))
723
+ d_p0 += d_A * 3 * (-1) * 2 * (-p0+3*p1-3*p2+p3);
724
+ d_p1 += d_A * 3 * 3 * 2 * (-p0+3*p1-3*p2+p3);
725
+ d_p2 += d_A * 3 * (-3) * 2 * (-p0+3*p1-3*p2+p3);
726
+ d_p3 += d_A * 3 * 1 * 2 * (-p0+3*p1-3*p2+p3);
727
+ // B = 5*sum((-p0+3*p1-3*p2+p3)*(3*p0-6*p1+3*p2))
728
+ d_p0 += d_B * 5 * ((-1) * (3*p0-6*p1+3*p2) + 3 * (-p0+3*p1-3*p2+p3));
729
+ d_p1 += d_B * 5 * (3 * (3*p0-6*p1+3*p2) + (-6) * (-p0+3*p1-3*p2+p3));
730
+ d_p2 += d_B * 5 * ((-3) * (3*p0-6*p1+3*p2) + 3 * (-p0+3*p1-3*p2+p3));
731
+ d_p3 += d_B * 5 * (3*p0-6*p1+3*p2);
732
+ // C = 4*sum((-p0+3*p1-3*p2+p3)*(-3*p0+3*p1)) + 2*sum((3*p0-6*p1+3*p2)*(3*p0-6*p1+3*p2))
733
+ d_p0 += d_C * 4 * ((-1) * (-3*p0+3*p1) + (-3) * (-p0+3*p1-3*p2+p3)) +
734
+ d_C * 2 * (3 * 2 * (3*p0-6*p1+3*p2));
735
+ d_p1 += d_C * 4 * (3 * (-3*p0+3*p1) + 3 * (-p0+3*p1-3*p2+p3)) +
736
+ d_C * 2 * ((-6) * 2 * (3*p0-6*p1+3*p2));
737
+ d_p2 += d_C * 4 * ((-3) * (-3*p0+3*p1)) +
738
+ d_C * 2 * (3 * 2 * (3*p0-6*p1+3*p2));
739
+ d_p3 += d_C * 4 * (-3*p0+3*p1);
740
+ // D = 3*(sum((3*p0-6*p1+3*p2)*(-3*p0+3*p1)) + sum((-p0+3*p1-3*p2+p3)*(p0-pt)))
741
+ d_p0 += d_D * 3 * (3 * (-3*p0+3*p1) + (-3) * (3*p0-6*p1+3*p2)) +
742
+ d_D * 3 * ((-1) * (p0-pt) + 1 * (-p0+3*p1-3*p2+p3));
743
+ d_p1 += d_D * 3 * ((-6) * (-3*p0+3*p1) + (3) * (3*p0-6*p1+3*p2)) +
744
+ d_D * 3 * (3 * (p0-pt));
745
+ d_p2 += d_D * 3 * (3 * (-3*p0+3*p1)) +
746
+ d_D * 3 * ((-3) * (p0-pt));
747
+ d_pt += d_D * 3 * ((-1) * (-p0+3*p1-3*p2+p3));
748
+ // E = sum((-3*p0+3*p1)*(-3*p0+3*p1)) + 2*sum((p0-pt)*(3*p0-6*p1+3*p2))
749
+ d_p0 += d_E * ((-3) * 2 * (-3*p0+3*p1)) +
750
+ d_E * 2 * (1 * (3*p0-6*p1+3*p2) + 3 * (p0-pt));
751
+ d_p1 += d_E * ( 3 * 2 * (-3*p0+3*p1)) +
752
+ d_E * 2 * ((-6) * (p0-pt));
753
+ d_p2 += d_E * 2 * ( 3 * (p0-pt));
754
+ d_pt += d_E * 2 * ((-1) * (3*p0-6*p1+3*p2));
755
+ // F = sum((p0-pt)*(-3*p0+3*p1))
756
+ d_p0 += d_F * (1 * (-3*p0+3*p1)) +
757
+ d_F * ((-3) * (p0-pt));
758
+ d_p1 += d_F * (3 * (p0-pt));
759
+ d_pt += d_F * ((-1) * (-3*p0+3*p1));
760
+ }
761
+ }
762
+ atomic_add(d_path.points + 2 * i0, d_p0);
763
+ atomic_add(d_path.points + 2 * i1, d_p1);
764
+ atomic_add(d_path.points + 2 * i2, d_p2);
765
+ atomic_add(d_path.points + 2 * i3, d_p3);
766
+ } else {
767
+ assert(false);
768
+ }
769
+ }
770
+
771
+ DEVICE
772
+ inline
773
+ void d_closest_point(const Rect &rect,
774
+ const Vector2f &pt,
775
+ const Vector2f &d_closest_pt,
776
+ Rect &d_rect,
777
+ Vector2f &d_pt) {
778
+ auto dist = [&](const Vector2f &p0, const Vector2f &p1) -> float {
779
+ // project pt to line
780
+ auto t = dot(pt - p0, p1 - p0) / dot(p1 - p0, p1 - p0);
781
+ if (t < 0) {
782
+ return distance(p0, pt);
783
+ } else if (t > 1) {
784
+ return distance(p1, pt);
785
+ } else {
786
+ return distance(p0 + t * (p1 - p0), pt);
787
+ }
788
+ // return 0;
789
+ };
790
+ auto left_top = rect.p_min;
791
+ auto right_top = Vector2f{rect.p_max.x, rect.p_min.y};
792
+ auto left_bottom = Vector2f{rect.p_min.x, rect.p_max.y};
793
+ auto right_bottom = rect.p_max;
794
+ auto left_dist = dist(left_top, left_bottom);
795
+ auto top_dist = dist(left_top, right_top);
796
+ auto right_dist = dist(right_top, right_bottom);
797
+ auto bottom_dist = dist(left_bottom, right_bottom);
798
+ int min_id = 0;
799
+ auto min_dist = left_dist;
800
+ if (top_dist < min_dist) { min_dist = top_dist; min_id = 1; }
801
+ if (right_dist < min_dist) { min_dist = right_dist; min_id = 2; }
802
+ if (bottom_dist < min_dist) { min_dist = bottom_dist; min_id = 3; }
803
+
804
+ auto d_update = [&](const Vector2f &p0, const Vector2f &p1,
805
+ const Vector2f &d_closest_pt,
806
+ Vector2f &d_p0, Vector2f &d_p1) {
807
+ // project pt to line
808
+ auto t = dot(pt - p0, p1 - p0) / dot(p1 - p0, p1 - p0);
809
+ if (t < 0) {
810
+ d_p0 += d_closest_pt;
811
+ } else if (t > 1) {
812
+ d_p1 += d_closest_pt;
813
+ } else {
814
+ // p = p0 + t * (p1 - p0)
815
+ auto d_p = d_closest_pt;
816
+ d_p0 += d_p * (1 - t);
817
+ d_p1 += d_p * t;
818
+ auto d_t = sum(d_p * (p1 - p0));
819
+ // t = dot(pt - p0, p1 - p0) / dot(p1 - p0, p1 - p0)
820
+ auto d_numerator = d_t / dot(p1 - p0, p1 - p0);
821
+ auto d_denominator = d_t * (-t) / dot(p1 - p0, p1 - p0);
822
+ // numerator = dot(pt - p0, p1 - p0)
823
+ d_pt += (p1 - p0) * d_numerator;
824
+ d_p1 += (pt - p0) * d_numerator;
825
+ d_p0 += ((p0 - p1) + (p0 - pt)) * d_numerator;
826
+ // denominator = dot(p1 - p0, p1 - p0)
827
+ d_p1 += 2 * (p1 - p0) * d_denominator;
828
+ d_p0 += 2 * (p0 - p1) * d_denominator;
829
+ }
830
+ };
831
+ auto d_left_top = Vector2f{0, 0};
832
+ auto d_right_top = Vector2f{0, 0};
833
+ auto d_left_bottom = Vector2f{0, 0};
834
+ auto d_right_bottom = Vector2f{0, 0};
835
+ if (min_id == 0) {
836
+ d_update(left_top, left_bottom, d_closest_pt, d_left_top, d_left_bottom);
837
+ } else if (min_id == 1) {
838
+ d_update(left_top, right_top, d_closest_pt, d_left_top, d_right_top);
839
+ } else if (min_id == 2) {
840
+ d_update(right_top, right_bottom, d_closest_pt, d_right_top, d_right_bottom);
841
+ } else {
842
+ assert(min_id == 3);
843
+ d_update(left_bottom, right_bottom, d_closest_pt, d_left_bottom, d_right_bottom);
844
+ }
845
+ auto d_p_min = Vector2f{0, 0};
846
+ auto d_p_max = Vector2f{0, 0};
847
+ // left_top = rect.p_min
848
+ // right_top = Vector2f{rect.p_max.x, rect.p_min.y}
849
+ // left_bottom = Vector2f{rect.p_min.x, rect.p_max.y}
850
+ // right_bottom = rect.p_max
851
+ d_p_min += d_left_top;
852
+ d_p_max.x += d_right_top.x;
853
+ d_p_min.y += d_right_top.y;
854
+ d_p_min.x += d_left_bottom.x;
855
+ d_p_max.y += d_left_bottom.y;
856
+ d_p_max += d_right_bottom;
857
+ atomic_add(d_rect.p_min, d_p_min);
858
+ atomic_add(d_rect.p_max, d_p_max);
859
+ }
860
+
861
+ DEVICE
862
+ inline
863
+ void d_closest_point(const Shape &shape,
864
+ const Vector2f &pt,
865
+ const Vector2f &d_closest_pt,
866
+ const ClosestPointPathInfo &path_info,
867
+ Shape &d_shape,
868
+ Vector2f &d_pt) {
869
+ switch (shape.type) {
870
+ case ShapeType::Circle:
871
+ d_closest_point(*(const Circle *)shape.ptr,
872
+ pt,
873
+ d_closest_pt,
874
+ *(Circle *)d_shape.ptr,
875
+ d_pt);
876
+ break;
877
+ case ShapeType::Ellipse:
878
+ // https://www.geometrictools.com/Documentation/DistancePointEllipseEllipsoid.pdf
879
+ assert(false);
880
+ break;
881
+ case ShapeType::Path:
882
+ d_closest_point(*(const Path *)shape.ptr,
883
+ pt,
884
+ d_closest_pt,
885
+ path_info,
886
+ *(Path *)d_shape.ptr,
887
+ d_pt);
888
+ break;
889
+ case ShapeType::Rect:
890
+ d_closest_point(*(const Rect *)shape.ptr,
891
+ pt,
892
+ d_closest_pt,
893
+ *(Rect *)d_shape.ptr,
894
+ d_pt);
895
+ break;
896
+ }
897
+ }
898
+
899
+ DEVICE
900
+ inline
901
+ void d_compute_distance(const Matrix3x3f &canvas_to_shape,
902
+ const Matrix3x3f &shape_to_canvas,
903
+ const Shape &shape,
904
+ const Vector2f &pt,
905
+ const Vector2f &closest_pt,
906
+ const ClosestPointPathInfo &path_info,
907
+ float d_dist,
908
+ Matrix3x3f &d_shape_to_canvas,
909
+ Shape &d_shape,
910
+ float *d_translation) {
911
+ if (distance_squared(pt, closest_pt) < 1e-10f) {
912
+ // The derivative at distance=0 is undefined
913
+ return;
914
+ }
915
+ assert(isfinite(d_dist));
916
+ // pt is in canvas space, transform it to shape's local space
917
+ auto local_pt = xform_pt(canvas_to_shape, pt);
918
+ auto local_closest_pt = xform_pt(canvas_to_shape, closest_pt);
919
+ // auto local_closest_pt = closest_point(shape, local_pt);
920
+ // auto closest_pt = xform_pt(shape_group.shape_to_canvas, local_closest_pt);
921
+ // auto dist = distance(closest_pt, pt);
922
+ auto d_pt = Vector2f{0, 0};
923
+ auto d_closest_pt = Vector2f{0, 0};
924
+ d_distance(closest_pt, pt, d_dist, d_closest_pt, d_pt);
925
+ assert(isfinite(d_pt));
926
+ assert(isfinite(d_closest_pt));
927
+ // auto closest_pt = xform_pt(shape_group.shape_to_canvas, local_closest_pt);
928
+ auto d_local_closest_pt = Vector2f{0, 0};
929
+ auto d_shape_to_canvas_ = Matrix3x3f();
930
+ d_xform_pt(shape_to_canvas, local_closest_pt, d_closest_pt,
931
+ d_shape_to_canvas_, d_local_closest_pt);
932
+ assert(isfinite(d_local_closest_pt));
933
+ auto d_local_pt = Vector2f{0, 0};
934
+ d_closest_point(shape, local_pt, d_local_closest_pt, path_info, d_shape, d_local_pt);
935
+ assert(isfinite(d_local_pt));
936
+ auto d_canvas_to_shape = Matrix3x3f();
937
+ d_xform_pt(canvas_to_shape,
938
+ pt,
939
+ d_local_pt,
940
+ d_canvas_to_shape,
941
+ d_pt);
942
+ // http://jack.valmadre.net/notes/2016/09/04/back-prop-differentials/#back-propagation-using-differentials
943
+ auto tc2s = transpose(canvas_to_shape);
944
+ d_shape_to_canvas_ += -tc2s * d_canvas_to_shape * tc2s;
945
+ atomic_add(&d_shape_to_canvas(0, 0), d_shape_to_canvas_);
946
+ if (d_translation != nullptr) {
947
+ atomic_add(d_translation, -d_pt);
948
+ }
949
+ }
config/base.yaml ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ default:
3
+ use_ycrcb: False
4
+ seginit:
5
+ type: circle
6
+ radius: 5
7
+ save:
8
+ init: false
9
+ image: false
10
+ output: true
11
+ video: false
12
+ loss: false
13
+ trainable:
14
+ bg: False
15
+ record: True
16
+ stroke: False
17
+ # num_segments: 4
18
+ num_iter: 500
19
+ lr_base:
20
+ bg: 0.01
21
+ point: 1
22
+ color: 0.01
23
+ stroke_width: null
24
+ stroke_color: null
25
+ coord_init:
26
+ type: sparse
27
+ seed: 0
28
+ loss:
29
+ use_l1_loss: false
30
+ use_distance_weighted_loss: true
31
+ xing_loss_weight: 0.01
32
+ bis_loss_weight: null
33
+
34
+
35
+ experiment_1x1:
36
+ path_schedule:
37
+ type: repeat
38
+ max_path: 1
39
+ schedule_each: 1
40
+
41
+ experiment_4x1:
42
+ path_schedule:
43
+ type: repeat
44
+ max_path: 4
45
+ schedule_each: 1
46
+
47
+ experiment_5x1:
48
+ path_schedule:
49
+ type: repeat
50
+ max_path: 5
51
+ schedule_each: 1
52
+
53
+ experiment_8x1:
54
+ path_schedule:
55
+ type: repeat
56
+ max_path: 8
57
+ schedule_each: 1
58
+
59
+ experiment_16x1:
60
+ path_schedule:
61
+ type: repeat
62
+ max_path: 16
63
+ schedule_each: 1
64
+
65
+ experiment_32x1:
66
+ path_schedule:
67
+ type: repeat
68
+ max_path: 32
69
+ schedule_each: 1
70
+
71
+ experiment_1357:
72
+ path_schedule:
73
+ type: list
74
+ schedule: [1, 3, 5, 7]
75
+
76
+
77
+ experiment_exp2_256:
78
+ path_schedule:
79
+ type: exp
80
+ base: 2
81
+ max_path: 256
82
+ max_path_per_iter: 32
83
+
84
+
85
+ experiment_exp2_128:
86
+ path_schedule:
87
+ type: exp
88
+ base: 2
89
+ max_path: 128
90
+ max_path_per_iter: 32
91
+
cuda_utils.h ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #pragma once
2
+
3
+ #ifdef __CUDACC__
4
+ #include <cuda.h>
5
+ #include <cuda_runtime.h>
6
+ #endif
7
+ #include <cstdio>
8
+ #include <cassert>
9
+ #include <limits>
10
+
11
+ #ifdef __CUDACC__
12
+ #define checkCuda(x) do { if((x)!=cudaSuccess) { \
13
+ printf("CUDA Runtime Error: %s at %s:%d\n",\
14
+ cudaGetErrorString(x),__FILE__,__LINE__);\
15
+ exit(1);}} while(0)
16
+ #endif
17
+
18
+ template <typename T>
19
+ DEVICE
20
+ inline T infinity() {
21
+ #ifdef __CUDA_ARCH__
22
+ const unsigned long long ieee754inf = 0x7ff0000000000000;
23
+ return __longlong_as_double(ieee754inf);
24
+ #else
25
+ return std::numeric_limits<T>::infinity();
26
+ #endif
27
+ }
28
+
29
+ template <>
30
+ DEVICE
31
+ inline double infinity() {
32
+ #ifdef __CUDA_ARCH__
33
+ return __longlong_as_double(0x7ff0000000000000ULL);
34
+ #else
35
+ return std::numeric_limits<double>::infinity();
36
+ #endif
37
+ }
38
+
39
+ template <>
40
+ DEVICE
41
+ inline float infinity() {
42
+ #ifdef __CUDA_ARCH__
43
+ return __int_as_float(0x7f800000);
44
+ #else
45
+ return std::numeric_limits<float>::infinity();
46
+ #endif
47
+ }
48
+
49
+ inline void cuda_synchronize() {
50
+ #ifdef __CUDACC__
51
+ checkCuda(cudaDeviceSynchronize());
52
+ #endif
53
+ }
data/demo1.png ADDED
data/demo2.jpg ADDED
data/demo3.png ADDED
diffvg.cpp ADDED
@@ -0,0 +1,1792 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #include "diffvg.h"
2
+ #include "aabb.h"
3
+ #include "shape.h"
4
+ #include "sample_boundary.h"
5
+ #include "atomic.h"
6
+ #include "cdf.h"
7
+ #include "compute_distance.h"
8
+ #include "cuda_utils.h"
9
+ #include "edge_query.h"
10
+ #include "filter.h"
11
+ #include "matrix.h"
12
+ #include "parallel.h"
13
+ #include "pcg.h"
14
+ #include "ptr.h"
15
+ #include "scene.h"
16
+ #include "vector.h"
17
+ #include "winding_number.h"
18
+ #include "within_distance.h"
19
+ #include <cassert>
20
+ #include <pybind11/pybind11.h>
21
+ #include <pybind11/stl.h>
22
+ #include <thrust/execution_policy.h>
23
+ #include <thrust/sort.h>
24
+
25
+ namespace py = pybind11;
26
+
27
+ struct Command {
28
+ int shape_group_id;
29
+ int shape_id;
30
+ int point_id; // Only used by path
31
+ };
32
+
33
+ DEVICE
34
+ bool is_inside(const SceneData &scene_data,
35
+ int shape_group_id,
36
+ const Vector2f &pt,
37
+ EdgeQuery *edge_query) {
38
+ const ShapeGroup &shape_group = scene_data.shape_groups[shape_group_id];
39
+ // pt is in canvas space, transform it to shape's local space
40
+ auto local_pt = xform_pt(shape_group.canvas_to_shape, pt);
41
+ const auto &bvh_nodes = scene_data.shape_groups_bvh_nodes[shape_group_id];
42
+ const AABB &bbox = bvh_nodes[2 * shape_group.num_shapes - 2].box;
43
+ if (!inside(bbox, local_pt)) {
44
+ return false;
45
+ }
46
+ auto winding_number = 0;
47
+ // Traverse the shape group BVH
48
+ constexpr auto max_bvh_stack_size = 64;
49
+ int bvh_stack[max_bvh_stack_size];
50
+ auto stack_size = 0;
51
+ bvh_stack[stack_size++] = 2 * shape_group.num_shapes - 2;
52
+ while (stack_size > 0) {
53
+ const BVHNode &node = bvh_nodes[bvh_stack[--stack_size]];
54
+ if (node.child1 < 0) {
55
+ // leaf
56
+ auto shape_id = node.child0;
57
+ auto w = compute_winding_number(
58
+ scene_data.shapes[shape_id], scene_data.path_bvhs[shape_id], local_pt);
59
+ winding_number += w;
60
+ if (edge_query != nullptr) {
61
+ if (edge_query->shape_group_id == shape_group_id &&
62
+ edge_query->shape_id == shape_id) {
63
+ if ((shape_group.use_even_odd_rule && abs(w) % 2 == 1) ||
64
+ (!shape_group.use_even_odd_rule && w != 0)) {
65
+ edge_query->hit = true;
66
+ }
67
+ }
68
+ }
69
+ } else {
70
+ assert(node.child0 >= 0 && node.child1 >= 0);
71
+ const AABB &b0 = bvh_nodes[node.child0].box;
72
+ if (inside(b0, local_pt)) {
73
+ bvh_stack[stack_size++] = node.child0;
74
+ }
75
+ const AABB &b1 = bvh_nodes[node.child1].box;
76
+ if (inside(b1, local_pt)) {
77
+ bvh_stack[stack_size++] = node.child1;
78
+ }
79
+ assert(stack_size <= max_bvh_stack_size);
80
+ }
81
+ }
82
+ if (shape_group.use_even_odd_rule) {
83
+ return abs(winding_number) % 2 == 1;
84
+ } else {
85
+ return winding_number != 0;
86
+ }
87
+ }
88
+
89
+ DEVICE void accumulate_boundary_gradient(const Shape &shape,
90
+ float contrib,
91
+ float t,
92
+ const Vector2f &normal,
93
+ const BoundaryData &boundary_data,
94
+ Shape &d_shape,
95
+ const Matrix3x3f &shape_to_canvas,
96
+ const Vector2f &local_boundary_pt,
97
+ Matrix3x3f &d_shape_to_canvas) {
98
+ assert(isfinite(contrib));
99
+ assert(isfinite(normal));
100
+ // According to Reynold transport theorem,
101
+ // the Jacobian of the boundary integral is dot(velocity, normal),
102
+ // where the velocity depends on the variable being differentiated with.
103
+ if (boundary_data.is_stroke) {
104
+ auto has_path_thickness = false;
105
+ if (shape.type == ShapeType::Path) {
106
+ const Path &path = *(const Path *)shape.ptr;
107
+ has_path_thickness = path.thickness != nullptr;
108
+ }
109
+ // differentiate stroke width: velocity is the same as normal
110
+ if (has_path_thickness) {
111
+ Path *d_p = (Path*)d_shape.ptr;
112
+ auto base_point_id = boundary_data.path.base_point_id;
113
+ auto point_id = boundary_data.path.point_id;
114
+ auto t = boundary_data.path.t;
115
+ const Path &path = *(const Path *)shape.ptr;
116
+ if (path.num_control_points[base_point_id] == 0) {
117
+ // Straight line
118
+ auto i0 = point_id;
119
+ auto i1 = (point_id + 1) % path.num_points;
120
+ // r = r0 + t * (r1 - r0)
121
+ atomic_add(&d_p->thickness[i0], (1 - t) * contrib);
122
+ atomic_add(&d_p->thickness[i1], ( t) * contrib);
123
+ } else if (path.num_control_points[base_point_id] == 1) {
124
+ // Quadratic Bezier curve
125
+ auto i0 = point_id;
126
+ auto i1 = point_id + 1;
127
+ auto i2 = (point_id + 2) % path.num_points;
128
+ // r = (1-t)^2r0 + 2(1-t)t r1 + t^2 r2
129
+ atomic_add(&d_p->thickness[i0], square(1 - t) * contrib);
130
+ atomic_add(&d_p->thickness[i1], (2*(1-t)*t) * contrib);
131
+ atomic_add(&d_p->thickness[i2], (t*t) * contrib);
132
+ } else if (path.num_control_points[base_point_id] == 2) {
133
+ auto i0 = point_id;
134
+ auto i1 = point_id + 1;
135
+ auto i2 = point_id + 2;
136
+ auto i3 = (point_id + 3) % path.num_points;
137
+ // r = (1-t)^3r0 + 3*(1-t)^2tr1 + 3*(1-t)t^2r2 + t^3r3
138
+ atomic_add(&d_p->thickness[i0], cubic(1 - t) * contrib);
139
+ atomic_add(&d_p->thickness[i1], 3 * square(1 - t) * t * contrib);
140
+ atomic_add(&d_p->thickness[i2], 3 * (1 - t) * t * t * contrib);
141
+ atomic_add(&d_p->thickness[i3], t * t * t * contrib);
142
+ } else {
143
+ assert(false);
144
+ }
145
+ } else {
146
+ atomic_add(&d_shape.stroke_width, contrib);
147
+ }
148
+ }
149
+ switch (shape.type) {
150
+ case ShapeType::Circle: {
151
+ Circle *d_p = (Circle*)d_shape.ptr;
152
+ // velocity for the center is (1, 0) for x and (0, 1) for y
153
+ atomic_add(&d_p->center[0], normal * contrib);
154
+ // velocity for the radius is the same as the normal
155
+ atomic_add(&d_p->radius, contrib);
156
+ break;
157
+ } case ShapeType::Ellipse: {
158
+ Ellipse *d_p = (Ellipse*)d_shape.ptr;
159
+ // velocity for the center is (1, 0) for x and (0, 1) for y
160
+ atomic_add(&d_p->center[0], normal * contrib);
161
+ // velocity for the radius:
162
+ // x = center.x + r.x * cos(2pi * t)
163
+ // y = center.y + r.y * sin(2pi * t)
164
+ // for r.x: (cos(2pi * t), 0)
165
+ // for r.y: (0, sin(2pi * t))
166
+ atomic_add(&d_p->radius.x, cos(2 * float(M_PI) * t) * normal.x * contrib);
167
+ atomic_add(&d_p->radius.y, sin(2 * float(M_PI) * t) * normal.y * contrib);
168
+ break;
169
+ } case ShapeType::Path: {
170
+ Path *d_p = (Path*)d_shape.ptr;
171
+ auto base_point_id = boundary_data.path.base_point_id;
172
+ auto point_id = boundary_data.path.point_id;
173
+ auto t = boundary_data.path.t;
174
+ const Path &path = *(const Path *)shape.ptr;
175
+ if (path.num_control_points[base_point_id] == 0) {
176
+ // Straight line
177
+ auto i0 = point_id;
178
+ auto i1 = (point_id + 1) % path.num_points;
179
+ // pt = p0 + t * (p1 - p0)
180
+ // velocity for p0.x: (1 - t, 0)
181
+ // p0.y: ( 0, 1 - t)
182
+ // p1.x: ( t, 0)
183
+ // p1.y: ( 0, t)
184
+ atomic_add(&d_p->points[2 * i0 + 0], (1 - t) * normal.x * contrib);
185
+ atomic_add(&d_p->points[2 * i0 + 1], (1 - t) * normal.y * contrib);
186
+ atomic_add(&d_p->points[2 * i1 + 0], ( t) * normal.x * contrib);
187
+ atomic_add(&d_p->points[2 * i1 + 1], ( t) * normal.y * contrib);
188
+ } else if (path.num_control_points[base_point_id] == 1) {
189
+ // Quadratic Bezier curve
190
+ auto i0 = point_id;
191
+ auto i1 = point_id + 1;
192
+ auto i2 = (point_id + 2) % path.num_points;
193
+ // pt = (1-t)^2p0 + 2(1-t)t p1 + t^2 p2
194
+ // velocity for p0.x: ((1-t)^2, 0)
195
+ // p0.y: ( 0, (1-t)^2)
196
+ // p1.x: (2(1-t)t, 0)
197
+ // p1.y: ( 0, 2(1-t)t)
198
+ // p1.x: ( t^2, 0)
199
+ // p1.y: ( 0, t^2)
200
+ atomic_add(&d_p->points[2 * i0 + 0], square(1 - t) * normal.x * contrib);
201
+ atomic_add(&d_p->points[2 * i0 + 1], square(1 - t) * normal.y * contrib);
202
+ atomic_add(&d_p->points[2 * i1 + 0], (2*(1-t)*t) * normal.x * contrib);
203
+ atomic_add(&d_p->points[2 * i1 + 1], (2*(1-t)*t) * normal.y * contrib);
204
+ atomic_add(&d_p->points[2 * i2 + 0], (t*t) * normal.x * contrib);
205
+ atomic_add(&d_p->points[2 * i2 + 1], (t*t) * normal.y * contrib);
206
+ } else if (path.num_control_points[base_point_id] == 2) {
207
+ auto i0 = point_id;
208
+ auto i1 = point_id + 1;
209
+ auto i2 = point_id + 2;
210
+ auto i3 = (point_id + 3) % path.num_points;
211
+ // pt = (1-t)^3p0 + 3*(1-t)^2tp1 + 3*(1-t)t^2p2 + t^3p3
212
+ // velocity for p0.x: ( (1-t)^3, 0)
213
+ // p0.y: ( 0, (1-t)^3)
214
+ // p1.x: (3*(1-t)^2t, 0)
215
+ // p1.y: ( 0, 3*(1-t)^2t)
216
+ // p2.x: (3*(1-t)t^2, 0)
217
+ // p2.y: ( 0, 3*(1-t)t^2)
218
+ // p2.x: ( t^3, 0)
219
+ // p2.y: ( 0, t^3)
220
+ atomic_add(&d_p->points[2 * i0 + 0], cubic(1 - t) * normal.x * contrib);
221
+ atomic_add(&d_p->points[2 * i0 + 1], cubic(1 - t) * normal.y * contrib);
222
+ atomic_add(&d_p->points[2 * i1 + 0], 3 * square(1 - t) * t * normal.x * contrib);
223
+ atomic_add(&d_p->points[2 * i1 + 1], 3 * square(1 - t) * t * normal.y * contrib);
224
+ atomic_add(&d_p->points[2 * i2 + 0], 3 * (1 - t) * t * t * normal.x * contrib);
225
+ atomic_add(&d_p->points[2 * i2 + 1], 3 * (1 - t) * t * t * normal.y * contrib);
226
+ atomic_add(&d_p->points[2 * i3 + 0], t * t * t * normal.x * contrib);
227
+ atomic_add(&d_p->points[2 * i3 + 1], t * t * t * normal.y * contrib);
228
+ } else {
229
+ assert(false);
230
+ }
231
+ break;
232
+ } case ShapeType::Rect: {
233
+ Rect *d_p = (Rect*)d_shape.ptr;
234
+ // The velocity depends on the position of the boundary
235
+ if (normal == Vector2f{-1, 0}) {
236
+ // left
237
+ // velocity for p_min is (1, 0) for x and (0, 0) for y
238
+ atomic_add(&d_p->p_min.x, -contrib);
239
+ } else if (normal == Vector2f{1, 0}) {
240
+ // right
241
+ // velocity for p_max is (1, 0) for x and (0, 0) for y
242
+ atomic_add(&d_p->p_max.x, contrib);
243
+ } else if (normal == Vector2f{0, -1}) {
244
+ // top
245
+ // velocity for p_min is (0, 0) for x and (0, 1) for y
246
+ atomic_add(&d_p->p_min.y, -contrib);
247
+ } else if (normal == Vector2f{0, 1}) {
248
+ // bottom
249
+ // velocity for p_max is (0, 0) for x and (0, 1) for y
250
+ atomic_add(&d_p->p_max.y, contrib);
251
+ } else {
252
+ // incorrect normal assignment?
253
+ assert(false);
254
+ }
255
+ break;
256
+ } default: {
257
+ assert(false);
258
+ break;
259
+ }
260
+ }
261
+ // for shape_to_canvas we have the following relationship:
262
+ // boundary_pt = xform_pt(shape_to_canvas, local_pt)
263
+ // the velocity is the derivative of boundary_pt with respect to shape_to_canvas
264
+ // we can use reverse-mode AD to compute the dot product of the velocity and the Jacobian
265
+ // by passing the normal in d_xform_pt
266
+ auto d_shape_to_canvas_ = Matrix3x3f();
267
+ auto d_local_boundary_pt = Vector2f{0, 0};
268
+ d_xform_pt(shape_to_canvas,
269
+ local_boundary_pt,
270
+ normal * contrib,
271
+ d_shape_to_canvas_,
272
+ d_local_boundary_pt);
273
+ atomic_add(&d_shape_to_canvas(0, 0), d_shape_to_canvas_);
274
+ }
275
+
276
+ DEVICE
277
+ Vector4f sample_color(const ColorType &color_type,
278
+ void *color,
279
+ const Vector2f &pt) {
280
+ switch (color_type) {
281
+ case ColorType::Constant: {
282
+ auto c = (const Constant*)color;
283
+ assert(isfinite(c->color));
284
+ return c->color;
285
+ } case ColorType::LinearGradient: {
286
+ auto c = (const LinearGradient*)color;
287
+ // Project pt to (c->begin, c->end)
288
+ auto beg = c->begin;
289
+ auto end = c->end;
290
+ auto t = dot(pt - beg, end - beg) / max(dot(end - beg, end - beg), 1e-3f);
291
+ // Find the correponding stop:
292
+ if (t < c->stop_offsets[0]) {
293
+ return Vector4f{c->stop_colors[0],
294
+ c->stop_colors[1],
295
+ c->stop_colors[2],
296
+ c->stop_colors[3]};
297
+ }
298
+ for (int i = 0; i < c->num_stops - 1; i++) {
299
+ auto offset_curr = c->stop_offsets[i];
300
+ auto offset_next = c->stop_offsets[i + 1];
301
+ assert(offset_next > offset_curr);
302
+ if (t >= offset_curr && t < offset_next) {
303
+ auto color_curr = Vector4f{
304
+ c->stop_colors[4 * i + 0],
305
+ c->stop_colors[4 * i + 1],
306
+ c->stop_colors[4 * i + 2],
307
+ c->stop_colors[4 * i + 3]};
308
+ auto color_next = Vector4f{
309
+ c->stop_colors[4 * (i + 1) + 0],
310
+ c->stop_colors[4 * (i + 1) + 1],
311
+ c->stop_colors[4 * (i + 1) + 2],
312
+ c->stop_colors[4 * (i + 1) + 3]};
313
+ auto tt = (t - offset_curr) / (offset_next - offset_curr);
314
+ assert(isfinite(tt));
315
+ assert(isfinite(color_curr));
316
+ assert(isfinite(color_next));
317
+ return color_curr * (1 - tt) + color_next * tt;
318
+ }
319
+ }
320
+ return Vector4f{c->stop_colors[4 * (c->num_stops - 1) + 0],
321
+ c->stop_colors[4 * (c->num_stops - 1) + 1],
322
+ c->stop_colors[4 * (c->num_stops - 1) + 2],
323
+ c->stop_colors[4 * (c->num_stops - 1) + 3]};
324
+ } case ColorType::RadialGradient: {
325
+ auto c = (const RadialGradient*)color;
326
+ // Distance from pt to center
327
+ auto offset = pt - c->center;
328
+ auto normalized_offset = offset / c->radius;
329
+ auto t = length(normalized_offset);
330
+ // Find the correponding stop:
331
+ if (t < c->stop_offsets[0]) {
332
+ return Vector4f{c->stop_colors[0],
333
+ c->stop_colors[1],
334
+ c->stop_colors[2],
335
+ c->stop_colors[3]};
336
+ }
337
+ for (int i = 0; i < c->num_stops - 1; i++) {
338
+ auto offset_curr = c->stop_offsets[i];
339
+ auto offset_next = c->stop_offsets[i + 1];
340
+ assert(offset_next > offset_curr);
341
+ if (t >= offset_curr && t < offset_next) {
342
+ auto color_curr = Vector4f{
343
+ c->stop_colors[4 * i + 0],
344
+ c->stop_colors[4 * i + 1],
345
+ c->stop_colors[4 * i + 2],
346
+ c->stop_colors[4 * i + 3]};
347
+ auto color_next = Vector4f{
348
+ c->stop_colors[4 * (i + 1) + 0],
349
+ c->stop_colors[4 * (i + 1) + 1],
350
+ c->stop_colors[4 * (i + 1) + 2],
351
+ c->stop_colors[4 * (i + 1) + 3]};
352
+ auto tt = (t - offset_curr) / (offset_next - offset_curr);
353
+ assert(isfinite(tt));
354
+ assert(isfinite(color_curr));
355
+ assert(isfinite(color_next));
356
+ return color_curr * (1 - tt) + color_next * tt;
357
+ }
358
+ }
359
+ return Vector4f{c->stop_colors[4 * (c->num_stops - 1) + 0],
360
+ c->stop_colors[4 * (c->num_stops - 1) + 1],
361
+ c->stop_colors[4 * (c->num_stops - 1) + 2],
362
+ c->stop_colors[4 * (c->num_stops - 1) + 3]};
363
+ } default: {
364
+ assert(false);
365
+ }
366
+ }
367
+ return Vector4f{};
368
+ }
369
+
370
+ DEVICE
371
+ void d_sample_color(const ColorType &color_type,
372
+ void *color_ptr,
373
+ const Vector2f &pt,
374
+ const Vector4f &d_color,
375
+ void *d_color_ptr,
376
+ float *d_translation) {
377
+ switch (color_type) {
378
+ case ColorType::Constant: {
379
+ auto d_c = (Constant*)d_color_ptr;
380
+ atomic_add(&d_c->color[0], d_color);
381
+ return;
382
+ } case ColorType::LinearGradient: {
383
+ auto c = (const LinearGradient*)color_ptr;
384
+ auto d_c = (LinearGradient*)d_color_ptr;
385
+ // Project pt to (c->begin, c->end)
386
+ auto beg = c->begin;
387
+ auto end = c->end;
388
+ auto t = dot(pt - beg, end - beg) / max(dot(end - beg, end - beg), 1e-3f);
389
+ // Find the correponding stop:
390
+ if (t < c->stop_offsets[0]) {
391
+ atomic_add(&d_c->stop_colors[0], d_color);
392
+ return;
393
+ }
394
+ for (int i = 0; i < c->num_stops - 1; i++) {
395
+ auto offset_curr = c->stop_offsets[i];
396
+ auto offset_next = c->stop_offsets[i + 1];
397
+ assert(offset_next > offset_curr);
398
+ if (t >= offset_curr && t < offset_next) {
399
+ auto color_curr = Vector4f{
400
+ c->stop_colors[4 * i + 0],
401
+ c->stop_colors[4 * i + 1],
402
+ c->stop_colors[4 * i + 2],
403
+ c->stop_colors[4 * i + 3]};
404
+ auto color_next = Vector4f{
405
+ c->stop_colors[4 * (i + 1) + 0],
406
+ c->stop_colors[4 * (i + 1) + 1],
407
+ c->stop_colors[4 * (i + 1) + 2],
408
+ c->stop_colors[4 * (i + 1) + 3]};
409
+ auto tt = (t - offset_curr) / (offset_next - offset_curr);
410
+ // return color_curr * (1 - tt) + color_next * tt;
411
+ auto d_color_curr = d_color * (1 - tt);
412
+ auto d_color_next = d_color * tt;
413
+ auto d_tt = sum(d_color * (color_next - color_curr));
414
+ auto d_offset_next = -d_tt * tt / (offset_next - offset_curr);
415
+ auto d_offset_curr = d_tt * ((tt - 1.f) / (offset_next - offset_curr));
416
+ auto d_t = d_tt / (offset_next - offset_curr);
417
+ assert(isfinite(d_tt));
418
+ atomic_add(&d_c->stop_colors[4 * i], d_color_curr);
419
+ atomic_add(&d_c->stop_colors[4 * (i + 1)], d_color_next);
420
+ atomic_add(&d_c->stop_offsets[i], d_offset_curr);
421
+ atomic_add(&d_c->stop_offsets[i + 1], d_offset_next);
422
+ // auto t = dot(pt - beg, end - beg) / max(dot(end - beg, end - beg), 1e-6f);
423
+ // l = max(dot(end - beg, end - beg), 1e-3f)
424
+ // t = dot(pt - beg, end - beg) / l;
425
+ auto l = max(dot(end - beg, end - beg), 1e-3f);
426
+ auto d_beg = d_t * (-(pt - beg)-(end - beg)) / l;
427
+ auto d_end = d_t * (pt - beg) / l;
428
+ auto d_l = -d_t * t / l;
429
+ if (dot(end - beg, end - beg) > 1e-3f) {
430
+ d_beg += 2 * d_l * (beg - end);
431
+ d_end += 2 * d_l * (end - beg);
432
+ }
433
+ atomic_add(&d_c->begin[0], d_beg);
434
+ atomic_add(&d_c->end[0], d_end);
435
+ if (d_translation != nullptr) {
436
+ atomic_add(d_translation, (d_beg + d_end));
437
+ }
438
+ return;
439
+ }
440
+ }
441
+ atomic_add(&d_c->stop_colors[4 * (c->num_stops - 1)], d_color);
442
+ return;
443
+ } case ColorType::RadialGradient: {
444
+ auto c = (const RadialGradient*)color_ptr;
445
+ auto d_c = (RadialGradient*)d_color_ptr;
446
+ // Distance from pt to center
447
+ auto offset = pt - c->center;
448
+ auto normalized_offset = offset / c->radius;
449
+ auto t = length(normalized_offset);
450
+ // Find the correponding stop:
451
+ if (t < c->stop_offsets[0]) {
452
+ atomic_add(&d_c->stop_colors[0], d_color);
453
+ return;
454
+ }
455
+ for (int i = 0; i < c->num_stops - 1; i++) {
456
+ auto offset_curr = c->stop_offsets[i];
457
+ auto offset_next = c->stop_offsets[i + 1];
458
+ assert(offset_next > offset_curr);
459
+ if (t >= offset_curr && t < offset_next) {
460
+ auto color_curr = Vector4f{
461
+ c->stop_colors[4 * i + 0],
462
+ c->stop_colors[4 * i + 1],
463
+ c->stop_colors[4 * i + 2],
464
+ c->stop_colors[4 * i + 3]};
465
+ auto color_next = Vector4f{
466
+ c->stop_colors[4 * (i + 1) + 0],
467
+ c->stop_colors[4 * (i + 1) + 1],
468
+ c->stop_colors[4 * (i + 1) + 2],
469
+ c->stop_colors[4 * (i + 1) + 3]};
470
+ auto tt = (t - offset_curr) / (offset_next - offset_curr);
471
+ assert(isfinite(tt));
472
+ // return color_curr * (1 - tt) + color_next * tt;
473
+ auto d_color_curr = d_color * (1 - tt);
474
+ auto d_color_next = d_color * tt;
475
+ auto d_tt = sum(d_color * (color_next - color_curr));
476
+ auto d_offset_next = -d_tt * tt / (offset_next - offset_curr);
477
+ auto d_offset_curr = d_tt * ((tt - 1.f) / (offset_next - offset_curr));
478
+ auto d_t = d_tt / (offset_next - offset_curr);
479
+ assert(isfinite(d_t));
480
+ atomic_add(&d_c->stop_colors[4 * i], d_color_curr);
481
+ atomic_add(&d_c->stop_colors[4 * (i + 1)], d_color_next);
482
+ atomic_add(&d_c->stop_offsets[i], d_offset_curr);
483
+ atomic_add(&d_c->stop_offsets[i + 1], d_offset_next);
484
+ // offset = pt - c->center
485
+ // normalized_offset = offset / c->radius
486
+ // t = length(normalized_offset)
487
+ auto d_normalized_offset = d_length(normalized_offset, d_t);
488
+ auto d_offset = d_normalized_offset / c->radius;
489
+ auto d_radius = -d_normalized_offset * offset / (c->radius * c->radius);
490
+ auto d_center = -d_offset;
491
+ atomic_add(&d_c->center[0], d_center);
492
+ atomic_add(&d_c->radius[0], d_radius);
493
+ if (d_translation != nullptr) {
494
+ atomic_add(d_translation, d_center);
495
+ }
496
+ }
497
+ }
498
+ atomic_add(&d_c->stop_colors[4 * (c->num_stops - 1)], d_color);
499
+ return;
500
+ } default: {
501
+ assert(false);
502
+ }
503
+ }
504
+ }
505
+
506
+ struct Fragment {
507
+ Vector3f color;
508
+ float alpha;
509
+ int group_id;
510
+ bool is_stroke;
511
+ };
512
+
513
+ struct PrefilterFragment {
514
+ Vector3f color;
515
+ float alpha;
516
+ int group_id;
517
+ bool is_stroke;
518
+ int shape_id;
519
+ float distance;
520
+ Vector2f closest_pt;
521
+ ClosestPointPathInfo path_info;
522
+ bool within_distance;
523
+ };
524
+
525
+ DEVICE
526
+ Vector4f sample_color(const SceneData &scene,
527
+ const Vector4f *background_color,
528
+ const Vector2f &screen_pt,
529
+ const Vector4f *d_color = nullptr,
530
+ EdgeQuery *edge_query = nullptr,
531
+ Vector4f *d_background_color = nullptr,
532
+ float *d_translation = nullptr) {
533
+ if (edge_query != nullptr) {
534
+ edge_query->hit = false;
535
+ }
536
+
537
+ // screen_pt is in screen space ([0, 1), [0, 1)),
538
+ // need to transform to canvas space
539
+ auto pt = screen_pt;
540
+ pt.x *= scene.canvas_width;
541
+ pt.y *= scene.canvas_height;
542
+ constexpr auto max_hit_shapes = 256;
543
+ constexpr auto max_bvh_stack_size = 64;
544
+ Fragment fragments[max_hit_shapes];
545
+ int bvh_stack[max_bvh_stack_size];
546
+ auto stack_size = 0;
547
+ auto num_fragments = 0;
548
+ bvh_stack[stack_size++] = 2 * scene.num_shape_groups - 2;
549
+ while (stack_size > 0) {
550
+ const BVHNode &node = scene.bvh_nodes[bvh_stack[--stack_size]];
551
+ if (node.child1 < 0) {
552
+ // leaf
553
+ auto group_id = node.child0;
554
+ const ShapeGroup &shape_group = scene.shape_groups[group_id];
555
+ if (shape_group.stroke_color != nullptr) {
556
+ if (within_distance(scene, group_id, pt, edge_query)) {
557
+ auto color_alpha = sample_color(shape_group.stroke_color_type,
558
+ shape_group.stroke_color,
559
+ pt);
560
+ Fragment f;
561
+ f.color = Vector3f{color_alpha[0], color_alpha[1], color_alpha[2]};
562
+ f.alpha = color_alpha[3];
563
+ f.group_id = group_id;
564
+ f.is_stroke = true;
565
+ assert(num_fragments < max_hit_shapes);
566
+ fragments[num_fragments++] = f;
567
+ }
568
+ }
569
+ if (shape_group.fill_color != nullptr) {
570
+ if (is_inside(scene, group_id, pt, edge_query)) {
571
+ auto color_alpha = sample_color(shape_group.fill_color_type,
572
+ shape_group.fill_color,
573
+ pt);
574
+ Fragment f;
575
+ f.color = Vector3f{color_alpha[0], color_alpha[1], color_alpha[2]};
576
+ f.alpha = color_alpha[3];
577
+ f.group_id = group_id;
578
+ f.is_stroke = false;
579
+ assert(num_fragments < max_hit_shapes);
580
+ fragments[num_fragments++] = f;
581
+ }
582
+ }
583
+ } else {
584
+ assert(node.child0 >= 0 && node.child1 >= 0);
585
+ const AABB &b0 = scene.bvh_nodes[node.child0].box;
586
+ if (inside(b0, pt, scene.bvh_nodes[node.child0].max_radius)) {
587
+ bvh_stack[stack_size++] = node.child0;
588
+ }
589
+ const AABB &b1 = scene.bvh_nodes[node.child1].box;
590
+ if (inside(b1, pt, scene.bvh_nodes[node.child1].max_radius)) {
591
+ bvh_stack[stack_size++] = node.child1;
592
+ }
593
+ assert(stack_size <= max_bvh_stack_size);
594
+ }
595
+ }
596
+ if (num_fragments <= 0) {
597
+ if (background_color != nullptr) {
598
+ if (d_background_color != nullptr) {
599
+ *d_background_color = *d_color;
600
+ }
601
+ return *background_color;
602
+ }
603
+ return Vector4f{0, 0, 0, 0};
604
+ }
605
+ // Sort the fragments from back to front (i.e. increasing order of group id)
606
+ // https://github.com/frigaut/yorick-imutil/blob/master/insort.c#L37
607
+ for (int i = 1; i < num_fragments; i++) {
608
+ auto j = i;
609
+ auto temp = fragments[j];
610
+ while (j > 0 && fragments[j - 1].group_id > temp.group_id) {
611
+ fragments[j] = fragments[j - 1];
612
+ j--;
613
+ }
614
+ fragments[j] = temp;
615
+ }
616
+ // Blend the color
617
+ Vector3f accum_color[max_hit_shapes];
618
+ float accum_alpha[max_hit_shapes];
619
+ // auto hit_opaque = false;
620
+ auto first_alpha = 0.f;
621
+ auto first_color = Vector3f{0, 0, 0};
622
+ if (background_color != nullptr) {
623
+ first_alpha = background_color->w;
624
+ first_color = Vector3f{background_color->x,
625
+ background_color->y,
626
+ background_color->z};
627
+ }
628
+ for (int i = 0; i < num_fragments; i++) {
629
+ const Fragment &fragment = fragments[i];
630
+ auto new_color = fragment.color;
631
+ auto new_alpha = fragment.alpha;
632
+ auto prev_alpha = i > 0 ? accum_alpha[i - 1] : first_alpha;
633
+ auto prev_color = i > 0 ? accum_color[i - 1] : first_color;
634
+ if (edge_query != nullptr) {
635
+ // Do we hit the target shape?
636
+ if (new_alpha >= 1.f && edge_query->hit) {
637
+ // A fully opaque shape in front of the target occludes it
638
+ edge_query->hit = false;
639
+ }
640
+ if (edge_query->shape_group_id == fragment.group_id) {
641
+ edge_query->hit = true;
642
+ }
643
+ }
644
+ // prev_color is alpha premultiplied, don't need to multiply with
645
+ // prev_alpha
646
+ accum_color[i] = prev_color * (1 - new_alpha) + new_alpha * new_color;
647
+ accum_alpha[i] = prev_alpha * (1 - new_alpha) + new_alpha;
648
+ }
649
+ auto final_color = accum_color[num_fragments - 1];
650
+ auto final_alpha = accum_alpha[num_fragments - 1];
651
+ if (final_alpha > 1e-6f) {
652
+ final_color /= final_alpha;
653
+ }
654
+ assert(isfinite(final_color));
655
+ assert(isfinite(final_alpha));
656
+ if (d_color != nullptr) {
657
+ // Backward pass
658
+ auto d_final_color = Vector3f{(*d_color)[0], (*d_color)[1], (*d_color)[2]};
659
+ auto d_final_alpha = (*d_color)[3];
660
+ auto d_curr_color = d_final_color;
661
+ auto d_curr_alpha = d_final_alpha;
662
+ if (final_alpha > 1e-6f) {
663
+ // final_color = curr_color / final_alpha
664
+ d_curr_color = d_final_color / final_alpha;
665
+ d_curr_alpha -= sum(d_final_color * final_color) / final_alpha;
666
+ }
667
+ assert(isfinite(*d_color));
668
+ assert(isfinite(d_curr_color));
669
+ assert(isfinite(d_curr_alpha));
670
+ for (int i = num_fragments - 1; i >= 0; i--) {
671
+ // color[n] = prev_color * (1 - new_alpha) + new_alpha * new_color;
672
+ // alpha[n] = prev_alpha * (1 - new_alpha) + new_alpha;
673
+ auto prev_alpha = i > 0 ? accum_alpha[i - 1] : first_alpha;
674
+ auto prev_color = i > 0 ? accum_color[i - 1] : first_color;
675
+ auto d_prev_alpha = d_curr_alpha * (1.f - fragments[i].alpha);
676
+ auto d_alpha_i = d_curr_alpha * (1.f - prev_alpha);
677
+ d_alpha_i += sum(d_curr_color * (fragments[i].color - prev_color));
678
+ auto d_prev_color = d_curr_color * (1 - fragments[i].alpha);
679
+ auto d_color_i = d_curr_color * fragments[i].alpha;
680
+ auto group_id = fragments[i].group_id;
681
+ if (fragments[i].is_stroke) {
682
+ d_sample_color(scene.shape_groups[group_id].stroke_color_type,
683
+ scene.shape_groups[group_id].stroke_color,
684
+ pt,
685
+ Vector4f{d_color_i[0], d_color_i[1], d_color_i[2], d_alpha_i},
686
+ scene.d_shape_groups[group_id].stroke_color,
687
+ d_translation);
688
+ } else {
689
+ d_sample_color(scene.shape_groups[group_id].fill_color_type,
690
+ scene.shape_groups[group_id].fill_color,
691
+ pt,
692
+ Vector4f{d_color_i[0], d_color_i[1], d_color_i[2], d_alpha_i},
693
+ scene.d_shape_groups[group_id].fill_color,
694
+ d_translation);
695
+ }
696
+ d_curr_color = d_prev_color;
697
+ d_curr_alpha = d_prev_alpha;
698
+ }
699
+ if (d_background_color != nullptr) {
700
+ d_background_color->x += d_curr_color.x;
701
+ d_background_color->y += d_curr_color.y;
702
+ d_background_color->z += d_curr_color.z;
703
+ d_background_color->w += d_curr_alpha;
704
+ }
705
+ }
706
+ return Vector4f{final_color[0], final_color[1], final_color[2], final_alpha};
707
+ }
708
+
709
+ DEVICE
710
+ float sample_distance(const SceneData &scene,
711
+ const Vector2f &screen_pt,
712
+ float weight,
713
+ const float *d_dist = nullptr,
714
+ float *d_translation = nullptr) {
715
+ // screen_pt is in screen space ([0, 1), [0, 1)),
716
+ // need to transform to canvas space
717
+ auto pt = screen_pt;
718
+ pt.x *= scene.canvas_width;
719
+ pt.y *= scene.canvas_height;
720
+ // for each shape
721
+ auto min_group_id = -1;
722
+ auto min_distance = 0.f;
723
+ auto min_shape_id = -1;
724
+ auto closest_pt = Vector2f{0, 0};
725
+ auto min_path_info = ClosestPointPathInfo{-1, -1, 0};
726
+ for (int group_id = scene.num_shape_groups - 1; group_id >= 0; group_id--) {
727
+ auto s = -1;
728
+ auto p = Vector2f{0, 0};
729
+ ClosestPointPathInfo local_path_info;
730
+ auto d = infinity<float>();
731
+ if (compute_distance(scene, group_id, pt, infinity<float>(), &s, &p, &local_path_info, &d)) {
732
+ if (min_group_id == -1 || d < min_distance) {
733
+ min_distance = d;
734
+ min_group_id = group_id;
735
+ min_shape_id = s;
736
+ closest_pt = p;
737
+ min_path_info = local_path_info;
738
+ }
739
+ }
740
+ }
741
+ if (min_group_id == -1) {
742
+ return min_distance;
743
+ }
744
+ min_distance *= weight;
745
+ auto inside = false;
746
+ const ShapeGroup &shape_group = scene.shape_groups[min_group_id];
747
+ if (shape_group.fill_color != nullptr) {
748
+ inside = is_inside(scene,
749
+ min_group_id,
750
+ pt,
751
+ nullptr);
752
+ if (inside) {
753
+ min_distance = -min_distance;
754
+ }
755
+ }
756
+ assert((min_group_id >= 0 && min_shape_id >= 0) || scene.num_shape_groups == 0);
757
+ if (d_dist != nullptr) {
758
+ auto d_abs_dist = inside ? -(*d_dist) : (*d_dist);
759
+ const ShapeGroup &shape_group = scene.shape_groups[min_group_id];
760
+ const Shape &shape = scene.shapes[min_shape_id];
761
+ ShapeGroup &d_shape_group = scene.d_shape_groups[min_group_id];
762
+ Shape &d_shape = scene.d_shapes[min_shape_id];
763
+ d_compute_distance(shape_group.canvas_to_shape,
764
+ shape_group.shape_to_canvas,
765
+ shape,
766
+ pt,
767
+ closest_pt,
768
+ min_path_info,
769
+ d_abs_dist,
770
+ d_shape_group.shape_to_canvas,
771
+ d_shape,
772
+ d_translation);
773
+ }
774
+ return min_distance;
775
+ }
776
+
777
+ // Gather d_color from d_image inside the filter kernel, normalize by
778
+ // weight_image.
779
+ DEVICE
780
+ Vector4f gather_d_color(const Filter &filter,
781
+ const float *d_color_image,
782
+ const float *weight_image,
783
+ int width,
784
+ int height,
785
+ const Vector2f &pt) {
786
+ auto x = int(pt.x);
787
+ auto y = int(pt.y);
788
+ auto radius = filter.radius;
789
+ assert(radius > 0);
790
+ auto ri = (int)ceil(radius);
791
+ auto d_color = Vector4f{0, 0, 0, 0};
792
+ for (int dy = -ri; dy <= ri; dy++) {
793
+ for (int dx = -ri; dx <= ri; dx++) {
794
+ auto xx = x + dx;
795
+ auto yy = y + dy;
796
+ if (xx >= 0 && xx < width && yy >= 0 && yy < height) {
797
+ auto xc = xx + 0.5f;
798
+ auto yc = yy + 0.5f;
799
+ auto filter_weight =
800
+ compute_filter_weight(filter, xc - pt.x, yc - pt.y);
801
+ // pixel = \sum weight * color / \sum weight
802
+ auto weight_sum = weight_image[yy * width + xx];
803
+ if (weight_sum > 0) {
804
+ d_color += (filter_weight / weight_sum) * Vector4f{
805
+ d_color_image[4 * (yy * width + xx) + 0],
806
+ d_color_image[4 * (yy * width + xx) + 1],
807
+ d_color_image[4 * (yy * width + xx) + 2],
808
+ d_color_image[4 * (yy * width + xx) + 3],
809
+ };
810
+ }
811
+ }
812
+ }
813
+ }
814
+ return d_color;
815
+ }
816
+
817
+ DEVICE
818
+ float smoothstep(float d) {
819
+ auto t = clamp((d + 1.f) / 2.f, 0.f, 1.f);
820
+ return t * t * (3 - 2 * t);
821
+ }
822
+
823
+ DEVICE
824
+ float d_smoothstep(float d, float d_ret) {
825
+ if (d < -1.f || d > 1.f) {
826
+ return 0.f;
827
+ }
828
+ auto t = (d + 1.f) / 2.f;
829
+ // ret = t * t * (3 - 2 * t)
830
+ // = 3 * t * t - 2 * t * t * t
831
+ auto d_t = d_ret * (6 * t - 6 * t * t);
832
+ return d_t / 2.f;
833
+ }
834
+
835
+ DEVICE
836
+ Vector4f sample_color_prefiltered(const SceneData &scene,
837
+ const Vector4f *background_color,
838
+ const Vector2f &screen_pt,
839
+ const Vector4f *d_color = nullptr,
840
+ Vector4f *d_background_color = nullptr,
841
+ float *d_translation = nullptr) {
842
+ // screen_pt is in screen space ([0, 1), [0, 1)),
843
+ // need to transform to canvas space
844
+ auto pt = screen_pt;
845
+ pt.x *= scene.canvas_width;
846
+ pt.y *= scene.canvas_height;
847
+ constexpr auto max_hit_shapes = 64;
848
+ constexpr auto max_bvh_stack_size = 64;
849
+ PrefilterFragment fragments[max_hit_shapes];
850
+ int bvh_stack[max_bvh_stack_size];
851
+ auto stack_size = 0;
852
+ auto num_fragments = 0;
853
+ bvh_stack[stack_size++] = 2 * scene.num_shape_groups - 2;
854
+ while (stack_size > 0) {
855
+ const BVHNode &node = scene.bvh_nodes[bvh_stack[--stack_size]];
856
+ if (node.child1 < 0) {
857
+ // leaf
858
+ auto group_id = node.child0;
859
+ const ShapeGroup &shape_group = scene.shape_groups[group_id];
860
+ if (shape_group.stroke_color != nullptr) {
861
+ auto min_shape_id = -1;
862
+ auto closest_pt = Vector2f{0, 0};
863
+ auto local_path_info = ClosestPointPathInfo{-1, -1, 0};
864
+ auto d = infinity<float>();
865
+ compute_distance(scene, group_id, pt, infinity<float>(),
866
+ &min_shape_id, &closest_pt, &local_path_info, &d);
867
+ assert(min_shape_id != -1);
868
+ const auto &shape = scene.shapes[min_shape_id];
869
+ auto w = smoothstep(fabs(d) + shape.stroke_width) -
870
+ smoothstep(fabs(d) - shape.stroke_width);
871
+ if (w > 0) {
872
+ auto color_alpha = sample_color(shape_group.stroke_color_type,
873
+ shape_group.stroke_color,
874
+ pt);
875
+ color_alpha[3] *= w;
876
+
877
+ PrefilterFragment f;
878
+ f.color = Vector3f{color_alpha[0], color_alpha[1], color_alpha[2]};
879
+ f.alpha = color_alpha[3];
880
+ f.group_id = group_id;
881
+ f.shape_id = min_shape_id;
882
+ f.distance = d;
883
+ f.closest_pt = closest_pt;
884
+ f.is_stroke = true;
885
+ f.path_info = local_path_info;
886
+ f.within_distance = true;
887
+ assert(num_fragments < max_hit_shapes);
888
+ fragments[num_fragments++] = f;
889
+ }
890
+ }
891
+ if (shape_group.fill_color != nullptr) {
892
+ auto min_shape_id = -1;
893
+ auto closest_pt = Vector2f{0, 0};
894
+ auto local_path_info = ClosestPointPathInfo{-1, -1, 0};
895
+ auto d = infinity<float>();
896
+ auto found = compute_distance(scene,
897
+ group_id,
898
+ pt,
899
+ 1.f,
900
+ &min_shape_id,
901
+ &closest_pt,
902
+ &local_path_info,
903
+ &d);
904
+ auto inside = is_inside(scene, group_id, pt, nullptr);
905
+ if (found || inside) {
906
+ if (!inside) {
907
+ d = -d;
908
+ }
909
+ auto w = smoothstep(d);
910
+ if (w > 0) {
911
+ auto color_alpha = sample_color(shape_group.fill_color_type,
912
+ shape_group.fill_color,
913
+ pt);
914
+ color_alpha[3] *= w;
915
+
916
+ PrefilterFragment f;
917
+ f.color = Vector3f{color_alpha[0], color_alpha[1], color_alpha[2]};
918
+ f.alpha = color_alpha[3];
919
+ f.group_id = group_id;
920
+ f.shape_id = min_shape_id;
921
+ f.distance = d;
922
+ f.closest_pt = closest_pt;
923
+ f.is_stroke = false;
924
+ f.path_info = local_path_info;
925
+ f.within_distance = found;
926
+ assert(num_fragments < max_hit_shapes);
927
+ fragments[num_fragments++] = f;
928
+ }
929
+ }
930
+ }
931
+ } else {
932
+ assert(node.child0 >= 0 && node.child1 >= 0);
933
+ const AABB &b0 = scene.bvh_nodes[node.child0].box;
934
+ if (inside(b0, pt, scene.bvh_nodes[node.child0].max_radius)) {
935
+ bvh_stack[stack_size++] = node.child0;
936
+ }
937
+ const AABB &b1 = scene.bvh_nodes[node.child1].box;
938
+ if (inside(b1, pt, scene.bvh_nodes[node.child1].max_radius)) {
939
+ bvh_stack[stack_size++] = node.child1;
940
+ }
941
+ assert(stack_size <= max_bvh_stack_size);
942
+ }
943
+ }
944
+ if (num_fragments <= 0) {
945
+ if (background_color != nullptr) {
946
+ if (d_background_color != nullptr) {
947
+ *d_background_color = *d_color;
948
+ }
949
+ return *background_color;
950
+ }
951
+ return Vector4f{0, 0, 0, 0};
952
+ }
953
+ // Sort the fragments from back to front (i.e. increasing order of group id)
954
+ // https://github.com/frigaut/yorick-imutil/blob/master/insort.c#L37
955
+ for (int i = 1; i < num_fragments; i++) {
956
+ auto j = i;
957
+ auto temp = fragments[j];
958
+ while (j > 0 && fragments[j - 1].group_id > temp.group_id) {
959
+ fragments[j] = fragments[j - 1];
960
+ j--;
961
+ }
962
+ fragments[j] = temp;
963
+ }
964
+ // Blend the color
965
+ Vector3f accum_color[max_hit_shapes];
966
+ float accum_alpha[max_hit_shapes];
967
+ auto first_alpha = 0.f;
968
+ auto first_color = Vector3f{0, 0, 0};
969
+ if (background_color != nullptr) {
970
+ first_alpha = background_color->w;
971
+ first_color = Vector3f{background_color->x,
972
+ background_color->y,
973
+ background_color->z};
974
+ }
975
+ for (int i = 0; i < num_fragments; i++) {
976
+ const PrefilterFragment &fragment = fragments[i];
977
+ auto new_color = fragment.color;
978
+ auto new_alpha = fragment.alpha;
979
+ auto prev_alpha = i > 0 ? accum_alpha[i - 1] : first_alpha;
980
+ auto prev_color = i > 0 ? accum_color[i - 1] : first_color;
981
+ // prev_color is alpha premultiplied, don't need to multiply with
982
+ // prev_alpha
983
+ accum_color[i] = prev_color * (1 - new_alpha) + new_alpha * new_color;
984
+ accum_alpha[i] = prev_alpha * (1 - new_alpha) + new_alpha;
985
+ }
986
+ auto final_color = accum_color[num_fragments - 1];
987
+ auto final_alpha = accum_alpha[num_fragments - 1];
988
+ if (final_alpha > 1e-6f) {
989
+ final_color /= final_alpha;
990
+ }
991
+ assert(isfinite(final_color));
992
+ assert(isfinite(final_alpha));
993
+ if (d_color != nullptr) {
994
+ // Backward pass
995
+ auto d_final_color = Vector3f{(*d_color)[0], (*d_color)[1], (*d_color)[2]};
996
+ auto d_final_alpha = (*d_color)[3];
997
+ auto d_curr_color = d_final_color;
998
+ auto d_curr_alpha = d_final_alpha;
999
+ if (final_alpha > 1e-6f) {
1000
+ // final_color = curr_color / final_alpha
1001
+ d_curr_color = d_final_color / final_alpha;
1002
+ d_curr_alpha -= sum(d_final_color * final_color) / final_alpha;
1003
+ }
1004
+ assert(isfinite(*d_color));
1005
+ assert(isfinite(d_curr_color));
1006
+ assert(isfinite(d_curr_alpha));
1007
+ for (int i = num_fragments - 1; i >= 0; i--) {
1008
+ // color[n] = prev_color * (1 - new_alpha) + new_alpha * new_color;
1009
+ // alpha[n] = prev_alpha * (1 - new_alpha) + new_alpha;
1010
+ auto prev_alpha = i > 0 ? accum_alpha[i - 1] : first_alpha;
1011
+ auto prev_color = i > 0 ? accum_color[i - 1] : first_color;
1012
+ auto d_prev_alpha = d_curr_alpha * (1.f - fragments[i].alpha);
1013
+ auto d_alpha_i = d_curr_alpha * (1.f - prev_alpha);
1014
+ d_alpha_i += sum(d_curr_color * (fragments[i].color - prev_color));
1015
+ auto d_prev_color = d_curr_color * (1 - fragments[i].alpha);
1016
+ auto d_color_i = d_curr_color * fragments[i].alpha;
1017
+ auto group_id = fragments[i].group_id;
1018
+ if (fragments[i].is_stroke) {
1019
+ const auto &shape = scene.shapes[fragments[i].shape_id];
1020
+ auto d = fragments[i].distance;
1021
+ auto abs_d_plus_width = fabs(d) + shape.stroke_width;
1022
+ auto abs_d_minus_width = fabs(d) - shape.stroke_width;
1023
+ auto w = smoothstep(abs_d_plus_width) -
1024
+ smoothstep(abs_d_minus_width);
1025
+ if (w != 0) {
1026
+ auto d_w = w > 0 ? (fragments[i].alpha / w) * d_alpha_i : 0.f;
1027
+ d_alpha_i *= w;
1028
+
1029
+ // Backprop to color
1030
+ d_sample_color(scene.shape_groups[group_id].stroke_color_type,
1031
+ scene.shape_groups[group_id].stroke_color,
1032
+ pt,
1033
+ Vector4f{d_color_i[0], d_color_i[1], d_color_i[2], d_alpha_i},
1034
+ scene.d_shape_groups[group_id].stroke_color,
1035
+ d_translation);
1036
+
1037
+ auto d_abs_d_plus_width = d_smoothstep(abs_d_plus_width, d_w);
1038
+ auto d_abs_d_minus_width = -d_smoothstep(abs_d_minus_width, d_w);
1039
+
1040
+ auto d_d = d_abs_d_plus_width + d_abs_d_minus_width;
1041
+ if (d < 0) {
1042
+ d_d = -d_d;
1043
+ }
1044
+ auto d_stroke_width = d_abs_d_plus_width - d_abs_d_minus_width;
1045
+
1046
+ const auto &shape_group = scene.shape_groups[group_id];
1047
+ ShapeGroup &d_shape_group = scene.d_shape_groups[group_id];
1048
+ Shape &d_shape = scene.d_shapes[fragments[i].shape_id];
1049
+ if (fabs(d_d) > 1e-10f) {
1050
+ d_compute_distance(shape_group.canvas_to_shape,
1051
+ shape_group.shape_to_canvas,
1052
+ shape,
1053
+ pt,
1054
+ fragments[i].closest_pt,
1055
+ fragments[i].path_info,
1056
+ d_d,
1057
+ d_shape_group.shape_to_canvas,
1058
+ d_shape,
1059
+ d_translation);
1060
+ }
1061
+ atomic_add(&d_shape.stroke_width, d_stroke_width);
1062
+ }
1063
+ } else {
1064
+ const auto &shape = scene.shapes[fragments[i].shape_id];
1065
+ auto d = fragments[i].distance;
1066
+ auto w = smoothstep(d);
1067
+ if (w != 0) {
1068
+ // color_alpha[3] = color_alpha[3] * w;
1069
+ auto d_w = w > 0 ? (fragments[i].alpha / w) * d_alpha_i : 0.f;
1070
+ d_alpha_i *= w;
1071
+
1072
+ d_sample_color(scene.shape_groups[group_id].fill_color_type,
1073
+ scene.shape_groups[group_id].fill_color,
1074
+ pt,
1075
+ Vector4f{d_color_i[0], d_color_i[1], d_color_i[2], d_alpha_i},
1076
+ scene.d_shape_groups[group_id].fill_color,
1077
+ d_translation);
1078
+
1079
+ // w = smoothstep(d)
1080
+ auto d_d = d_smoothstep(d, d_w);
1081
+ if (d < 0) {
1082
+ d_d = -d_d;
1083
+ }
1084
+
1085
+ const auto &shape_group = scene.shape_groups[group_id];
1086
+ ShapeGroup &d_shape_group = scene.d_shape_groups[group_id];
1087
+ Shape &d_shape = scene.d_shapes[fragments[i].shape_id];
1088
+ if (fabs(d_d) > 1e-10f && fragments[i].within_distance) {
1089
+ d_compute_distance(shape_group.canvas_to_shape,
1090
+ shape_group.shape_to_canvas,
1091
+ shape,
1092
+ pt,
1093
+ fragments[i].closest_pt,
1094
+ fragments[i].path_info,
1095
+ d_d,
1096
+ d_shape_group.shape_to_canvas,
1097
+ d_shape,
1098
+ d_translation);
1099
+ }
1100
+ }
1101
+ }
1102
+ d_curr_color = d_prev_color;
1103
+ d_curr_alpha = d_prev_alpha;
1104
+ }
1105
+ if (d_background_color != nullptr) {
1106
+ d_background_color->x += d_curr_color.x;
1107
+ d_background_color->y += d_curr_color.y;
1108
+ d_background_color->z += d_curr_color.z;
1109
+ d_background_color->w += d_curr_alpha;
1110
+ }
1111
+ }
1112
+ return Vector4f{final_color[0], final_color[1], final_color[2], final_alpha};
1113
+ }
1114
+
1115
+ struct weight_kernel {
1116
+ DEVICE void operator()(int idx) {
1117
+ auto rng_state = init_pcg32(idx, seed);
1118
+ // height * width * num_samples_y * num_samples_x
1119
+ auto sx = idx % num_samples_x;
1120
+ auto sy = (idx / num_samples_x) % num_samples_y;
1121
+ auto x = (idx / (num_samples_x * num_samples_y)) % width;
1122
+ auto y = (idx / (num_samples_x * num_samples_y * width));
1123
+ assert(y < height);
1124
+ auto rx = next_pcg32_float(&rng_state);
1125
+ auto ry = next_pcg32_float(&rng_state);
1126
+ if (use_prefiltering) {
1127
+ rx = ry = 0.5f;
1128
+ }
1129
+ auto pt = Vector2f{x + ((float)sx + rx) / num_samples_x,
1130
+ y + ((float)sy + ry) / num_samples_y};
1131
+ auto radius = scene.filter->radius;
1132
+ assert(radius >= 0);
1133
+ auto ri = (int)ceil(radius);
1134
+ for (int dy = -ri; dy <= ri; dy++) {
1135
+ for (int dx = -ri; dx <= ri; dx++) {
1136
+ auto xx = x + dx;
1137
+ auto yy = y + dy;
1138
+ if (xx >= 0 && xx < width && yy >= 0 && yy < height) {
1139
+ auto xc = xx + 0.5f;
1140
+ auto yc = yy + 0.5f;
1141
+ auto filter_weight = compute_filter_weight(*scene.filter,
1142
+ xc - pt.x,
1143
+ yc - pt.y);
1144
+ atomic_add(weight_image[yy * width + xx], filter_weight);
1145
+ }
1146
+ }
1147
+ }
1148
+ }
1149
+
1150
+ SceneData scene;
1151
+ float *weight_image;
1152
+ int width;
1153
+ int height;
1154
+ int num_samples_x;
1155
+ int num_samples_y;
1156
+ uint64_t seed;
1157
+ bool use_prefiltering;
1158
+ };
1159
+
1160
+ // We use a "mega kernel" for rendering
1161
+ struct render_kernel {
1162
+ DEVICE void operator()(int idx) {
1163
+ // height * width * num_samples_y * num_samples_x
1164
+ auto pt = Vector2f{0, 0};
1165
+ auto x = 0;
1166
+ auto y = 0;
1167
+ if (eval_positions == nullptr) {
1168
+ auto rng_state = init_pcg32(idx, seed);
1169
+ auto sx = idx % num_samples_x;
1170
+ auto sy = (idx / num_samples_x) % num_samples_y;
1171
+ x = (idx / (num_samples_x * num_samples_y)) % width;
1172
+ y = (idx / (num_samples_x * num_samples_y * width));
1173
+ assert(x < width && y < height);
1174
+ auto rx = next_pcg32_float(&rng_state);
1175
+ auto ry = next_pcg32_float(&rng_state);
1176
+ if (use_prefiltering) {
1177
+ rx = ry = 0.5f;
1178
+ }
1179
+ pt = Vector2f{x + ((float)sx + rx) / num_samples_x,
1180
+ y + ((float)sy + ry) / num_samples_y};
1181
+ } else {
1182
+ pt = Vector2f{eval_positions[2 * idx],
1183
+ eval_positions[2 * idx + 1]};
1184
+ x = int(pt.x);
1185
+ y = int(pt.y);
1186
+ }
1187
+
1188
+ // normalize pt to [0, 1]
1189
+ auto npt = pt;
1190
+ npt.x /= width;
1191
+ npt.y /= height;
1192
+ auto num_samples = num_samples_x * num_samples_y;
1193
+ if (render_image != nullptr || d_render_image != nullptr) {
1194
+ Vector4f d_color = Vector4f{0, 0, 0, 0};
1195
+ if (d_render_image != nullptr) {
1196
+ // Gather d_color from d_render_image inside the filter kernel
1197
+ // normalize using weight_image
1198
+ d_color = gather_d_color(*scene.filter,
1199
+ d_render_image,
1200
+ weight_image,
1201
+ width,
1202
+ height,
1203
+ pt);
1204
+ }
1205
+ auto color = Vector4f{0, 0, 0, 0};
1206
+ if (use_prefiltering) {
1207
+ color = sample_color_prefiltered(scene,
1208
+ background_image != nullptr ? (const Vector4f*)&background_image[4 * ((y * width) + x)] : nullptr,
1209
+ npt,
1210
+ d_render_image != nullptr ? &d_color : nullptr,
1211
+ d_background_image != nullptr ? (Vector4f*)&d_background_image[4 * ((y * width) + x)] : nullptr,
1212
+ d_translation != nullptr ? &d_translation[2 * (y * width + x)] : nullptr);
1213
+ } else {
1214
+ color = sample_color(scene,
1215
+ background_image != nullptr ? (const Vector4f*)&background_image[4 * ((y * width) + x)] : nullptr,
1216
+ npt,
1217
+ d_render_image != nullptr ? &d_color : nullptr,
1218
+ nullptr,
1219
+ d_background_image != nullptr ? (Vector4f*)&d_background_image[4 * ((y * width) + x)] : nullptr,
1220
+ d_translation != nullptr ? &d_translation[2 * (y * width + x)] : nullptr);
1221
+ }
1222
+ assert(isfinite(color));
1223
+ // Splat color onto render_image
1224
+ auto radius = scene.filter->radius;
1225
+ assert(radius >= 0);
1226
+ auto ri = (int)ceil(radius);
1227
+ for (int dy = -ri; dy <= ri; dy++) {
1228
+ for (int dx = -ri; dx <= ri; dx++) {
1229
+ auto xx = x + dx;
1230
+ auto yy = y + dy;
1231
+ if (xx >= 0 && xx < width && yy >= 0 && yy < height &&
1232
+ weight_image[yy * width + xx] > 0) {
1233
+ auto weight_sum = weight_image[yy * width + xx];
1234
+ auto xc = xx + 0.5f;
1235
+ auto yc = yy + 0.5f;
1236
+ auto filter_weight = compute_filter_weight(*scene.filter,
1237
+ xc - pt.x,
1238
+ yc - pt.y);
1239
+ auto weighted_color = filter_weight * color / weight_sum;
1240
+ if (render_image != nullptr) {
1241
+ atomic_add(render_image[4 * (yy * width + xx) + 0],
1242
+ weighted_color[0]);
1243
+ atomic_add(render_image[4 * (yy * width + xx) + 1],
1244
+ weighted_color[1]);
1245
+ atomic_add(render_image[4 * (yy * width + xx) + 2],
1246
+ weighted_color[2]);
1247
+ atomic_add(render_image[4 * (yy * width + xx) + 3],
1248
+ weighted_color[3]);
1249
+ }
1250
+ if (d_render_image != nullptr) {
1251
+ // Backprop to filter_weight
1252
+ // pixel = \sum weight * color / \sum weight
1253
+ auto d_pixel = Vector4f{
1254
+ d_render_image[4 * (yy * width + xx) + 0],
1255
+ d_render_image[4 * (yy * width + xx) + 1],
1256
+ d_render_image[4 * (yy * width + xx) + 2],
1257
+ d_render_image[4 * (yy * width + xx) + 3],
1258
+ };
1259
+ auto d_weight =
1260
+ (dot(d_pixel, color) * weight_sum -
1261
+ filter_weight * dot(d_pixel, color) * (weight_sum - filter_weight)) /
1262
+ square(weight_sum);
1263
+ d_compute_filter_weight(*scene.filter,
1264
+ xc - pt.x,
1265
+ yc - pt.y,
1266
+ d_weight,
1267
+ scene.d_filter);
1268
+ }
1269
+ }
1270
+ }
1271
+ }
1272
+ }
1273
+ if (sdf_image != nullptr || d_sdf_image != nullptr) {
1274
+ float d_dist = 0.f;
1275
+ if (d_sdf_image != nullptr) {
1276
+ if (eval_positions == nullptr) {
1277
+ d_dist = d_sdf_image[y * width + x];
1278
+ } else {
1279
+ d_dist = d_sdf_image[idx];
1280
+ }
1281
+ }
1282
+ auto weight = eval_positions == nullptr ? 1.f / num_samples : 1.f;
1283
+ auto dist = sample_distance(scene, npt, weight,
1284
+ d_sdf_image != nullptr ? &d_dist : nullptr,
1285
+ d_translation != nullptr ? &d_translation[2 * (y * width + x)] : nullptr);
1286
+ if (sdf_image != nullptr) {
1287
+ if (eval_positions == nullptr) {
1288
+ atomic_add(sdf_image[y * width + x], dist);
1289
+ } else {
1290
+ atomic_add(sdf_image[idx], dist);
1291
+ }
1292
+ }
1293
+ }
1294
+ }
1295
+
1296
+ SceneData scene;
1297
+ float *background_image;
1298
+ float *render_image;
1299
+ float *weight_image;
1300
+ float *sdf_image;
1301
+ float *d_background_image;
1302
+ float *d_render_image;
1303
+ float *d_sdf_image;
1304
+ float *d_translation;
1305
+ int width;
1306
+ int height;
1307
+ int num_samples_x;
1308
+ int num_samples_y;
1309
+ uint64_t seed;
1310
+ bool use_prefiltering;
1311
+ float *eval_positions;
1312
+ };
1313
+
1314
+ struct BoundarySample {
1315
+ Vector2f pt;
1316
+ Vector2f local_pt;
1317
+ Vector2f normal;
1318
+ int shape_group_id;
1319
+ int shape_id;
1320
+ float t;
1321
+ BoundaryData data;
1322
+ float pdf;
1323
+ };
1324
+
1325
+ struct sample_boundary_kernel {
1326
+ DEVICE void operator()(int idx) {
1327
+ boundary_samples[idx].pt = Vector2f{0, 0};
1328
+ boundary_samples[idx].shape_id = -1;
1329
+ boundary_ids[idx] = idx;
1330
+ morton_codes[idx] = 0;
1331
+
1332
+ auto rng_state = init_pcg32(idx, seed);
1333
+ auto u = next_pcg32_float(&rng_state);
1334
+ // Sample a shape
1335
+ auto sample_id = sample(scene.sample_shapes_cdf,
1336
+ scene.num_total_shapes,
1337
+ u);
1338
+ assert(sample_id >= 0 && sample_id < scene.num_total_shapes);
1339
+ auto shape_id = scene.sample_shape_id[sample_id];
1340
+ assert(shape_id >= 0 && shape_id < scene.num_shapes);
1341
+ auto shape_group_id = scene.sample_group_id[sample_id];
1342
+ assert(shape_group_id >= 0 && shape_group_id < scene.num_shape_groups);
1343
+ auto shape_pmf = scene.sample_shapes_pmf[shape_id];
1344
+ if (shape_pmf <= 0) {
1345
+ return;
1346
+ }
1347
+ // Sample a point on the boundary of the shape
1348
+ auto boundary_pdf = 0.f;
1349
+ auto normal = Vector2f{0, 0};
1350
+ auto t = next_pcg32_float(&rng_state);
1351
+ BoundaryData boundary_data;
1352
+ const ShapeGroup &shape_group = scene.shape_groups[shape_group_id];
1353
+ auto local_boundary_pt = sample_boundary(
1354
+ scene, shape_group_id, shape_id,
1355
+ t, normal, boundary_pdf, boundary_data);
1356
+ if (boundary_pdf <= 0) {
1357
+ return;
1358
+ }
1359
+
1360
+ // local_boundary_pt & normal are in shape's local space,
1361
+ // transform them to canvas space
1362
+ auto boundary_pt = xform_pt(shape_group.shape_to_canvas, local_boundary_pt);
1363
+ normal = xform_normal(shape_group.canvas_to_shape, normal);
1364
+ // Normalize boundary_pt to [0, 1)
1365
+ boundary_pt.x /= scene.canvas_width;
1366
+ boundary_pt.y /= scene.canvas_height;
1367
+
1368
+ boundary_samples[idx].pt = boundary_pt;
1369
+ boundary_samples[idx].local_pt = local_boundary_pt;
1370
+ boundary_samples[idx].normal = normal;
1371
+ boundary_samples[idx].shape_group_id = shape_group_id;
1372
+ boundary_samples[idx].shape_id = shape_id;
1373
+ boundary_samples[idx].t = t;
1374
+ boundary_samples[idx].data = boundary_data;
1375
+ boundary_samples[idx].pdf = shape_pmf * boundary_pdf;
1376
+ TVector2<uint32_t> p_i{boundary_pt.x * 1023, boundary_pt.y * 1023};
1377
+ morton_codes[idx] = (expand_bits(p_i.x) << 1u) |
1378
+ (expand_bits(p_i.y) << 0u);
1379
+ }
1380
+
1381
+ SceneData scene;
1382
+ uint64_t seed;
1383
+ BoundarySample *boundary_samples;
1384
+ int *boundary_ids;
1385
+ uint32_t *morton_codes;
1386
+ };
1387
+
1388
+ struct render_edge_kernel {
1389
+ DEVICE void operator()(int idx) {
1390
+ auto bid = boundary_ids[idx];
1391
+ if (boundary_samples[bid].shape_id == -1) {
1392
+ return;
1393
+ }
1394
+ auto boundary_pt = boundary_samples[bid].pt;
1395
+ auto local_boundary_pt = boundary_samples[bid].local_pt;
1396
+ auto normal = boundary_samples[bid].normal;
1397
+ auto shape_group_id = boundary_samples[bid].shape_group_id;
1398
+ auto shape_id = boundary_samples[bid].shape_id;
1399
+ auto t = boundary_samples[bid].t;
1400
+ auto boundary_data = boundary_samples[bid].data;
1401
+ auto pdf = boundary_samples[bid].pdf;
1402
+
1403
+ const ShapeGroup &shape_group = scene.shape_groups[shape_group_id];
1404
+
1405
+ auto bx = int(boundary_pt.x * width);
1406
+ auto by = int(boundary_pt.y * height);
1407
+ if (bx < 0 || bx >= width || by < 0 || by >= height) {
1408
+ return;
1409
+ }
1410
+
1411
+ // Sample the two sides of the boundary
1412
+ auto inside_query = EdgeQuery{shape_group_id, shape_id, false};
1413
+ auto outside_query = EdgeQuery{shape_group_id, shape_id, false};
1414
+ auto color_inside = sample_color(scene,
1415
+ background_image != nullptr ? (const Vector4f *)&background_image[4 * ((by * width) + bx)] : nullptr,
1416
+ boundary_pt - 1e-4f * normal,
1417
+ nullptr, &inside_query);
1418
+ auto color_outside = sample_color(scene,
1419
+ background_image != nullptr ? (const Vector4f *)&background_image[4 * ((by * width) + bx)] : nullptr,
1420
+ boundary_pt + 1e-4f * normal,
1421
+ nullptr, &outside_query);
1422
+ if (!inside_query.hit && !outside_query.hit) {
1423
+ // occluded
1424
+ return;
1425
+ }
1426
+ if (!inside_query.hit) {
1427
+ normal = -normal;
1428
+ swap_(inside_query, outside_query);
1429
+ swap_(color_inside, color_outside);
1430
+ }
1431
+ // Boundary point in screen space
1432
+ auto sboundary_pt = boundary_pt;
1433
+ sboundary_pt.x *= width;
1434
+ sboundary_pt.y *= height;
1435
+ auto d_color = gather_d_color(*scene.filter,
1436
+ d_render_image,
1437
+ weight_image,
1438
+ width,
1439
+ height,
1440
+ sboundary_pt);
1441
+ // Normalization factor
1442
+ d_color /= float(scene.canvas_width * scene.canvas_height);
1443
+
1444
+ assert(isfinite(d_color));
1445
+ assert(isfinite(pdf) && pdf > 0);
1446
+ auto contrib = dot(color_inside - color_outside, d_color) / pdf;
1447
+ ShapeGroup &d_shape_group = scene.d_shape_groups[shape_group_id];
1448
+ accumulate_boundary_gradient(scene.shapes[shape_id],
1449
+ contrib, t, normal, boundary_data, scene.d_shapes[shape_id],
1450
+ shape_group.shape_to_canvas, local_boundary_pt, d_shape_group.shape_to_canvas);
1451
+ // Don't need to backprop to filter weights:
1452
+ // \int f'(x) g(x) dx doesn't contain discontinuities
1453
+ // if f is continuous, even if g is discontinuous
1454
+ if (d_translation != nullptr) {
1455
+ // According to Reynold transport theorem,
1456
+ // the Jacobian of the boundary integral is dot(velocity, normal)
1457
+ // The velocity of the object translating x is (1, 0)
1458
+ // The velocity of the object translating y is (0, 1)
1459
+ atomic_add(&d_translation[2 * (by * width + bx) + 0], normal.x * contrib);
1460
+ atomic_add(&d_translation[2 * (by * width + bx) + 1], normal.y * contrib);
1461
+ }
1462
+ }
1463
+
1464
+ SceneData scene;
1465
+ const float *background_image;
1466
+ const BoundarySample *boundary_samples;
1467
+ const int *boundary_ids;
1468
+ float *weight_image;
1469
+ float *d_render_image;
1470
+ float *d_translation;
1471
+ int width;
1472
+ int height;
1473
+ int num_samples_x;
1474
+ int num_samples_y;
1475
+ };
1476
+
1477
+ void render(std::shared_ptr<Scene> scene,
1478
+ ptr<float> background_image,
1479
+ ptr<float> render_image,
1480
+ ptr<float> render_sdf,
1481
+ int width,
1482
+ int height,
1483
+ int num_samples_x,
1484
+ int num_samples_y,
1485
+ uint64_t seed,
1486
+ ptr<float> d_background_image,
1487
+ ptr<float> d_render_image,
1488
+ ptr<float> d_render_sdf,
1489
+ ptr<float> d_translation,
1490
+ bool use_prefiltering,
1491
+ ptr<float> eval_positions,
1492
+ int num_eval_positions) {
1493
+ #ifdef __NVCC__
1494
+ int old_device_id = -1;
1495
+ if (scene->use_gpu) {
1496
+ checkCuda(cudaGetDevice(&old_device_id));
1497
+ if (scene->gpu_index != -1) {
1498
+ checkCuda(cudaSetDevice(scene->gpu_index));
1499
+ }
1500
+ }
1501
+ #endif
1502
+ parallel_init();
1503
+
1504
+ float *weight_image = nullptr;
1505
+ // Allocate and zero the weight image
1506
+ if (scene->use_gpu) {
1507
+ #ifdef __CUDACC__
1508
+ if (eval_positions.get() == nullptr) {
1509
+ checkCuda(cudaMallocManaged(&weight_image, width * height * sizeof(float)));
1510
+ cudaMemset(weight_image, 0, width * height * sizeof(float));
1511
+ }
1512
+ #else
1513
+ assert(false);
1514
+ #endif
1515
+ } else {
1516
+ if (eval_positions.get() == nullptr) {
1517
+ weight_image = (float*)malloc(width * height * sizeof(float));
1518
+ memset(weight_image, 0, width * height * sizeof(float));
1519
+ }
1520
+ }
1521
+
1522
+ if (render_image.get() != nullptr || d_render_image.get() != nullptr ||
1523
+ render_sdf.get() != nullptr || d_render_sdf.get() != nullptr) {
1524
+ if (weight_image != nullptr) {
1525
+ parallel_for(weight_kernel{
1526
+ get_scene_data(*scene.get()),
1527
+ weight_image,
1528
+ width,
1529
+ height,
1530
+ num_samples_x,
1531
+ num_samples_y,
1532
+ seed
1533
+ }, width * height * num_samples_x * num_samples_y, scene->use_gpu);
1534
+ }
1535
+
1536
+ auto num_samples = eval_positions.get() == nullptr ?
1537
+ width * height * num_samples_x * num_samples_y : num_eval_positions;
1538
+ parallel_for(render_kernel{
1539
+ get_scene_data(*scene.get()),
1540
+ background_image.get(),
1541
+ render_image.get(),
1542
+ weight_image,
1543
+ render_sdf.get(),
1544
+ d_background_image.get(),
1545
+ d_render_image.get(),
1546
+ d_render_sdf.get(),
1547
+ d_translation.get(),
1548
+ width,
1549
+ height,
1550
+ num_samples_x,
1551
+ num_samples_y,
1552
+ seed,
1553
+ use_prefiltering,
1554
+ eval_positions.get()
1555
+ }, num_samples, scene->use_gpu);
1556
+ }
1557
+
1558
+ // Boundary sampling
1559
+ if (!use_prefiltering && d_render_image.get() != nullptr) {
1560
+ auto num_samples = width * height * num_samples_x * num_samples_y;
1561
+ BoundarySample *boundary_samples = nullptr;
1562
+ int *boundary_ids = nullptr; // for sorting
1563
+ uint32_t *morton_codes = nullptr; // for sorting
1564
+ // Allocate boundary samples
1565
+ if (scene->use_gpu) {
1566
+ #ifdef __CUDACC__
1567
+ checkCuda(cudaMallocManaged(&boundary_samples,
1568
+ num_samples * sizeof(BoundarySample)));
1569
+ checkCuda(cudaMallocManaged(&boundary_ids,
1570
+ num_samples * sizeof(int)));
1571
+ checkCuda(cudaMallocManaged(&morton_codes,
1572
+ num_samples * sizeof(uint32_t)));
1573
+ #else
1574
+ assert(false);
1575
+ #endif
1576
+ } else {
1577
+ boundary_samples = (BoundarySample*)malloc(
1578
+ num_samples * sizeof(BoundarySample));
1579
+ boundary_ids = (int*)malloc(
1580
+ num_samples * sizeof(int));
1581
+ morton_codes = (uint32_t*)malloc(
1582
+ num_samples * sizeof(uint32_t));
1583
+ }
1584
+
1585
+ // Edge sampling
1586
+ // We sort the boundary samples for better thread coherency
1587
+ parallel_for(sample_boundary_kernel{
1588
+ get_scene_data(*scene.get()),
1589
+ seed,
1590
+ boundary_samples,
1591
+ boundary_ids,
1592
+ morton_codes
1593
+ }, num_samples, scene->use_gpu);
1594
+ if (scene->use_gpu) {
1595
+ thrust::sort_by_key(thrust::device, morton_codes, morton_codes + num_samples, boundary_ids);
1596
+ } else {
1597
+ // Don't need to sort for CPU, we are not using SIMD hardware anyway.
1598
+ // thrust::sort_by_key(thrust::host, morton_codes, morton_codes + num_samples, boundary_ids);
1599
+ }
1600
+ parallel_for(render_edge_kernel{
1601
+ get_scene_data(*scene.get()),
1602
+ background_image.get(),
1603
+ boundary_samples,
1604
+ boundary_ids,
1605
+ weight_image,
1606
+ d_render_image.get(),
1607
+ d_translation.get(),
1608
+ width,
1609
+ height,
1610
+ num_samples_x,
1611
+ num_samples_y
1612
+ }, num_samples, scene->use_gpu);
1613
+ if (scene->use_gpu) {
1614
+ #ifdef __CUDACC__
1615
+ checkCuda(cudaFree(boundary_samples));
1616
+ checkCuda(cudaFree(boundary_ids));
1617
+ checkCuda(cudaFree(morton_codes));
1618
+ #else
1619
+ assert(false);
1620
+ #endif
1621
+ } else {
1622
+ free(boundary_samples);
1623
+ free(boundary_ids);
1624
+ free(morton_codes);
1625
+ }
1626
+ }
1627
+
1628
+ // Clean up weight image
1629
+ if (scene->use_gpu) {
1630
+ #ifdef __CUDACC__
1631
+ checkCuda(cudaFree(weight_image));
1632
+ #else
1633
+ assert(false);
1634
+ #endif
1635
+ } else {
1636
+ free(weight_image);
1637
+ }
1638
+
1639
+ if (scene->use_gpu) {
1640
+ cuda_synchronize();
1641
+ }
1642
+
1643
+ parallel_cleanup();
1644
+ #ifdef __NVCC__
1645
+ if (old_device_id != -1) {
1646
+ checkCuda(cudaSetDevice(old_device_id));
1647
+ }
1648
+ #endif
1649
+ }
1650
+
1651
+ PYBIND11_MODULE(diffvg, m) {
1652
+ m.doc() = "Differential Vector Graphics";
1653
+
1654
+ py::class_<ptr<void>>(m, "void_ptr")
1655
+ .def(py::init<std::size_t>())
1656
+ .def("as_size_t", &ptr<void>::as_size_t);
1657
+ py::class_<ptr<float>>(m, "float_ptr")
1658
+ .def(py::init<std::size_t>());
1659
+ py::class_<ptr<int>>(m, "int_ptr")
1660
+ .def(py::init<std::size_t>());
1661
+
1662
+ py::class_<Vector2f>(m, "Vector2f")
1663
+ .def(py::init<float, float>())
1664
+ .def_readwrite("x", &Vector2f::x)
1665
+ .def_readwrite("y", &Vector2f::y);
1666
+
1667
+ py::class_<Vector3f>(m, "Vector3f")
1668
+ .def(py::init<float, float, float>())
1669
+ .def_readwrite("x", &Vector3f::x)
1670
+ .def_readwrite("y", &Vector3f::y)
1671
+ .def_readwrite("z", &Vector3f::z);
1672
+
1673
+ py::class_<Vector4f>(m, "Vector4f")
1674
+ .def(py::init<float, float, float, float>())
1675
+ .def_readwrite("x", &Vector4f::x)
1676
+ .def_readwrite("y", &Vector4f::y)
1677
+ .def_readwrite("z", &Vector4f::z)
1678
+ .def_readwrite("w", &Vector4f::w);
1679
+
1680
+ py::enum_<ShapeType>(m, "ShapeType")
1681
+ .value("circle", ShapeType::Circle)
1682
+ .value("ellipse", ShapeType::Ellipse)
1683
+ .value("path", ShapeType::Path)
1684
+ .value("rect", ShapeType::Rect);
1685
+
1686
+ py::class_<Circle>(m, "Circle")
1687
+ .def(py::init<float, Vector2f>())
1688
+ .def("get_ptr", &Circle::get_ptr)
1689
+ .def_readonly("radius", &Circle::radius)
1690
+ .def_readonly("center", &Circle::center);
1691
+
1692
+ py::class_<Ellipse>(m, "Ellipse")
1693
+ .def(py::init<Vector2f, Vector2f>())
1694
+ .def("get_ptr", &Ellipse::get_ptr)
1695
+ .def_readonly("radius", &Ellipse::radius)
1696
+ .def_readonly("center", &Ellipse::center);
1697
+
1698
+ py::class_<Path>(m, "Path")
1699
+ .def(py::init<ptr<int>, ptr<float>, ptr<float>, int, int, bool, bool>())
1700
+ .def("get_ptr", &Path::get_ptr)
1701
+ .def("has_thickness", &Path::has_thickness)
1702
+ .def("copy_to", &Path::copy_to)
1703
+ .def_readonly("num_points", &Path::num_points);
1704
+
1705
+ py::class_<Rect>(m, "Rect")
1706
+ .def(py::init<Vector2f, Vector2f>())
1707
+ .def("get_ptr", &Rect::get_ptr)
1708
+ .def_readonly("p_min", &Rect::p_min)
1709
+ .def_readonly("p_max", &Rect::p_max);
1710
+
1711
+ py::enum_<ColorType>(m, "ColorType")
1712
+ .value("constant", ColorType::Constant)
1713
+ .value("linear_gradient", ColorType::LinearGradient)
1714
+ .value("radial_gradient", ColorType::RadialGradient);
1715
+
1716
+ py::class_<Constant>(m, "Constant")
1717
+ .def(py::init<Vector4f>())
1718
+ .def("get_ptr", &Constant::get_ptr)
1719
+ .def_readonly("color", &Constant::color);
1720
+
1721
+ py::class_<LinearGradient>(m, "LinearGradient")
1722
+ .def(py::init<Vector2f, Vector2f, int, ptr<float>, ptr<float>>())
1723
+ .def("get_ptr", &LinearGradient::get_ptr)
1724
+ .def("copy_to", &LinearGradient::copy_to)
1725
+ .def_readonly("begin", &LinearGradient::begin)
1726
+ .def_readonly("end", &LinearGradient::end)
1727
+ .def_readonly("num_stops", &LinearGradient::num_stops);
1728
+
1729
+ py::class_<RadialGradient>(m, "RadialGradient")
1730
+ .def(py::init<Vector2f, Vector2f, int, ptr<float>, ptr<float>>())
1731
+ .def("get_ptr", &RadialGradient::get_ptr)
1732
+ .def("copy_to", &RadialGradient::copy_to)
1733
+ .def_readonly("center", &RadialGradient::center)
1734
+ .def_readonly("radius", &RadialGradient::radius)
1735
+ .def_readonly("num_stops", &RadialGradient::num_stops);
1736
+
1737
+ py::class_<Shape>(m, "Shape")
1738
+ .def(py::init<ShapeType, ptr<void>, float>())
1739
+ .def("as_circle", &Shape::as_circle)
1740
+ .def("as_ellipse", &Shape::as_ellipse)
1741
+ .def("as_path", &Shape::as_path)
1742
+ .def("as_rect", &Shape::as_rect)
1743
+ .def_readonly("type", &Shape::type)
1744
+ .def_readonly("stroke_width", &Shape::stroke_width);
1745
+
1746
+ py::class_<ShapeGroup>(m, "ShapeGroup")
1747
+ .def(py::init<ptr<int>,
1748
+ int,
1749
+ ColorType,
1750
+ ptr<void>,
1751
+ ColorType,
1752
+ ptr<void>,
1753
+ bool,
1754
+ ptr<float>>())
1755
+ .def("fill_color_as_constant", &ShapeGroup::fill_color_as_constant)
1756
+ .def("fill_color_as_linear_gradient", &ShapeGroup::fill_color_as_linear_gradient)
1757
+ .def("fill_color_as_radial_gradient", &ShapeGroup::fill_color_as_radial_gradient)
1758
+ .def("stroke_color_as_constant", &ShapeGroup::stroke_color_as_constant)
1759
+ .def("stroke_color_as_linear_gradient", &ShapeGroup::stroke_color_as_linear_gradient)
1760
+ .def("stroke_color_as_radial_gradient", &ShapeGroup::fill_color_as_radial_gradient)
1761
+ .def("has_fill_color", &ShapeGroup::has_fill_color)
1762
+ .def("has_stroke_color", &ShapeGroup::has_stroke_color)
1763
+ .def("copy_to", &ShapeGroup::copy_to)
1764
+ .def_readonly("fill_color_type", &ShapeGroup::fill_color_type)
1765
+ .def_readonly("stroke_color_type", &ShapeGroup::stroke_color_type);
1766
+
1767
+ py::enum_<FilterType>(m, "FilterType")
1768
+ .value("box", FilterType::Box)
1769
+ .value("tent", FilterType::Tent)
1770
+ .value("parabolic", FilterType::RadialParabolic)
1771
+ .value("hann", FilterType::Hann);
1772
+
1773
+ py::class_<Filter>(m, "Filter")
1774
+ .def(py::init<FilterType,
1775
+ float>());
1776
+
1777
+ py::class_<Scene, std::shared_ptr<Scene>>(m, "Scene")
1778
+ .def(py::init<int,
1779
+ int,
1780
+ const std::vector<const Shape*> &,
1781
+ const std::vector<const ShapeGroup*> &,
1782
+ const Filter &,
1783
+ bool,
1784
+ int>())
1785
+ .def("get_d_shape", &Scene::get_d_shape)
1786
+ .def("get_d_shape_group", &Scene::get_d_shape_group)
1787
+ .def("get_d_filter_radius", &Scene::get_d_filter_radius)
1788
+ .def_readonly("num_shapes", &Scene::num_shapes)
1789
+ .def_readonly("num_shape_groups", &Scene::num_shape_groups);
1790
+
1791
+ m.def("render", &render, "");
1792
+ }
diffvg.h ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #pragma once
2
+
3
+ #ifdef __NVCC__
4
+ #define DEVICE __device__ __host__
5
+ #else
6
+ #define DEVICE
7
+ #endif
8
+
9
+ #ifndef __NVCC__
10
+ #include <cmath>
11
+ namespace {
12
+ inline float fmodf(float a, float b) {
13
+ return std::fmod(a, b);
14
+ }
15
+ inline double fmod(double a, double b) {
16
+ return std::fmod(a, b);
17
+ }
18
+ }
19
+ using std::isfinite;
20
+ #endif
21
+
22
+ #ifndef M_PI
23
+ #define M_PI 3.14159265358979323846
24
+ #endif
25
+
26
+ #include <cstdint>
27
+ #include <atomic>
28
+
29
+ // We use Real for most of the internal computation.
30
+ // However, for PyTorch interfaces, Optix Prime and Embree queries
31
+ // we use float
32
+ using Real = float;
33
+
34
+ template <typename T>
35
+ DEVICE
36
+ inline T square(const T &x) {
37
+ return x * x;
38
+ }
39
+
40
+ template <typename T>
41
+ DEVICE
42
+ inline T cubic(const T &x) {
43
+ return x * x * x;
44
+ }
45
+
46
+ template <typename T>
47
+ DEVICE
48
+ inline T clamp(const T &v, const T &lo, const T &hi) {
49
+ if (v < lo) return lo;
50
+ else if (v > hi) return hi;
51
+ else return v;
52
+ }
53
+
54
+ DEVICE
55
+ inline int modulo(int a, int b) {
56
+ auto r = a % b;
57
+ return (r < 0) ? r+b : r;
58
+ }
59
+
60
+ DEVICE
61
+ inline float modulo(float a, float b) {
62
+ float r = ::fmodf(a, b);
63
+ return (r < 0.0f) ? r+b : r;
64
+ }
65
+
66
+ DEVICE
67
+ inline double modulo(double a, double b) {
68
+ double r = ::fmod(a, b);
69
+ return (r < 0.0) ? r+b : r;
70
+ }
71
+
72
+ template <typename T>
73
+ DEVICE
74
+ inline T max(const T &a, const T &b) {
75
+ return a > b ? a : b;
76
+ }
77
+
78
+ template <typename T>
79
+ DEVICE
80
+ inline T min(const T &a, const T &b) {
81
+ return a < b ? a : b;
82
+ }
83
+
84
+ /// Return ceil(x/y) for integers x and y
85
+ inline int idiv_ceil(int x, int y) {
86
+ return (x + y-1) / y;
87
+ }
88
+
89
+ template <typename T>
90
+ DEVICE
91
+ inline void swap_(T &a, T &b) {
92
+ T tmp = a;
93
+ a = b;
94
+ b = tmp;
95
+ }
96
+
97
+ inline double log2(double x) {
98
+ return log(x) / log(Real(2));
99
+ }
100
+
101
+ template <typename T>
102
+ DEVICE
103
+ inline T safe_acos(const T &x) {
104
+ if (x >= 1) return T(0);
105
+ else if(x <= -1) return T(M_PI);
106
+ return acos(x);
107
+ }
108
+
109
+ // For Morton code computation. This can be made faster.
110
+ DEVICE
111
+ inline uint32_t expand_bits(uint32_t x) {
112
+ // Insert one zero after every bit given a 10-bit integer
113
+ constexpr uint64_t mask = 0x1u;
114
+ // We start from LSB (bit 31)
115
+ auto result = (x & (mask << 0u));
116
+ result |= ((x & (mask << 1u)) << 1u);
117
+ result |= ((x & (mask << 2u)) << 2u);
118
+ result |= ((x & (mask << 3u)) << 3u);
119
+ result |= ((x & (mask << 4u)) << 4u);
120
+ result |= ((x & (mask << 5u)) << 5u);
121
+ result |= ((x & (mask << 6u)) << 6u);
122
+ result |= ((x & (mask << 7u)) << 7u);
123
+ result |= ((x & (mask << 8u)) << 8u);
124
+ result |= ((x & (mask << 9u)) << 9u);
125
+ return result;
126
+ }
127
+
128
+ // DEVICE
129
+ // inline int clz(uint64_t x) {
130
+ // #ifdef __CUDA_ARCH__
131
+ // return __clzll(x);
132
+ // #else
133
+ // // TODO: use _BitScanReverse in windows
134
+ // return x == 0 ? 64 : __builtin_clzll(x);
135
+ // #endif
136
+ // }
137
+
138
+ // DEVICE
139
+ // inline int ffs(uint8_t x) {
140
+ // #ifdef __CUDA_ARCH__
141
+ // return __ffs(x);
142
+ // #else
143
+ // // TODO: use _BitScanReverse in windows
144
+ // return __builtin_ffs(x);
145
+ // #endif
146
+ // }
147
+
148
+ // DEVICE
149
+ // inline int popc(uint8_t x) {
150
+ // #ifdef __CUDA_ARCH__
151
+ // return __popc(x);
152
+ // #else
153
+ // // TODO: use _popcnt in windows
154
+ // return __builtin_popcount(x);
155
+ // #endif
156
+ // }
edge_query.h ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ #pragma once
2
+
3
+ struct EdgeQuery {
4
+ int shape_group_id;
5
+ int shape_id;
6
+ bool hit; // Do we hit the specified shape_group_id & shape_id?
7
+ };
examples/1.png ADDED
examples/2.png ADDED
examples/3.jpg ADDED
examples/4.png ADDED
examples/5.png ADDED
figures/smile.png ADDED
filter.h ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #pragma once
2
+
3
+ #include "diffvg.h"
4
+ #include "atomic.h"
5
+
6
+ enum class FilterType {
7
+ Box,
8
+ Tent,
9
+ RadialParabolic, // 4/3(1 - (d/r))
10
+ Hann // https://en.wikipedia.org/wiki/Window_function#Hann_and_Hamming_windows
11
+ };
12
+
13
+ struct Filter {
14
+ FilterType type;
15
+ float radius;
16
+ };
17
+
18
+ struct DFilter {
19
+ float radius;
20
+ };
21
+
22
+ DEVICE
23
+ inline
24
+ float compute_filter_weight(const Filter &filter,
25
+ float dx,
26
+ float dy) {
27
+ if (fabs(dx) > filter.radius || fabs(dy) > filter.radius) {
28
+ return 0;
29
+ }
30
+ if (filter.type == FilterType::Box) {
31
+ return 1.f / square(2 * filter.radius);
32
+ } else if (filter.type == FilterType::Tent) {
33
+ return (filter.radius - fabs(dx)) * (filter.radius - fabs(dy)) /
34
+ square(square(filter.radius));
35
+ } else if (filter.type == FilterType::RadialParabolic) {
36
+ return (4.f / 3.f) * (1 - square(dx / filter.radius)) *
37
+ (4.f / 3.f) * (1 - square(dy / filter.radius));
38
+ } else {
39
+ assert(filter.type == FilterType::Hann);
40
+ // normalize dx, dy to [0, 1]
41
+ auto ndx = (dx / (2*filter.radius)) + 0.5f;
42
+ auto ndy = (dy / (2*filter.radius)) + 0.5f;
43
+ // the normalization factor is R^2
44
+ return 0.5f * (1.f - cos(float(2 * M_PI) * ndx)) *
45
+ 0.5f * (1.f - cos(float(2 * M_PI) * ndy)) /
46
+ square(filter.radius);
47
+ }
48
+ }
49
+
50
+ DEVICE
51
+ inline
52
+ void d_compute_filter_weight(const Filter &filter,
53
+ float dx,
54
+ float dy,
55
+ float d_return,
56
+ DFilter *d_filter) {
57
+ if (filter.type == FilterType::Box) {
58
+ // return 1.f / square(2 * filter.radius);
59
+ atomic_add(d_filter->radius,
60
+ d_return * (-2) * 2 * filter.radius / cubic(2 * filter.radius));
61
+ } else if (filter.type == FilterType::Tent) {
62
+ // return (filer.radius - fabs(dx)) * (filer.radius - fabs(dy)) /
63
+ // square(square(filter.radius));
64
+ auto fx = filter.radius - fabs(dx);
65
+ auto fy = filter.radius - fabs(dy);
66
+ auto norm = 1 / square(filter.radius);
67
+ auto d_fx = d_return * fy * norm;
68
+ auto d_fy = d_return * fx * norm;
69
+ auto d_norm = d_return * fx * fy;
70
+ atomic_add(d_filter->radius,
71
+ d_fx + d_fy + (-4) * d_norm / pow(filter.radius, 5));
72
+ } else if (filter.type == FilterType::RadialParabolic) {
73
+ // return (4.f / 3.f) * (1 - square(dx / filter.radius)) *
74
+ // (4.f / 3.f) * (1 - square(dy / filter.radius));
75
+ // auto d_square_x = d_return * (-4.f / 3.f);
76
+ // auto d_square_y = d_return * (-4.f / 3.f);
77
+ auto r3 = filter.radius * filter.radius * filter.radius;
78
+ auto d_radius = -(2 * square(dx) + 2 * square(dy)) / r3;
79
+ atomic_add(d_filter->radius, d_radius);
80
+ } else {
81
+ assert(filter.type == FilterType::Hann);
82
+ // // normalize dx, dy to [0, 1]
83
+ // auto ndx = (dx / (2*filter.radius)) + 0.5f;
84
+ // auto ndy = (dy / (2*filter.radius)) + 0.5f;
85
+ // // the normalization factor is R^2
86
+ // return 0.5f * (1.f - cos(float(2 * M_PI) * ndx)) *
87
+ // 0.5f * (1.f - cos(float(2 * M_PI) * ndy)) /
88
+ // square(filter.radius);
89
+
90
+ // normalize dx, dy to [0, 1]
91
+ auto ndx = (dx / (2*filter.radius)) + 0.5f;
92
+ auto ndy = (dy / (2*filter.radius)) + 0.5f;
93
+ auto fx = 0.5f * (1.f - cos(float(2*M_PI) * ndx));
94
+ auto fy = 0.5f * (1.f - cos(float(2*M_PI) * ndy));
95
+ auto norm = 1 / square(filter.radius);
96
+ auto d_fx = d_return * fy * norm;
97
+ auto d_fy = d_return * fx * norm;
98
+ auto d_norm = d_return * fx * fy;
99
+ auto d_ndx = d_fx * 0.5f * sin(float(2*M_PI) * ndx) * float(2*M_PI);
100
+ auto d_ndy = d_fy * 0.5f * sin(float(2*M_PI) * ndy) * float(2*M_PI);
101
+ atomic_add(d_filter->radius,
102
+ d_ndx * (-2*dx / square(2*filter.radius)) +
103
+ d_ndy * (-2*dy / square(2*filter.radius)) +
104
+ (-2) * d_norm / cubic(filter.radius));
105
+ }
106
+ }
icon/logo.ico ADDED
img_example/Millenial-at-work.jpg ADDED
img_example/bus.jpg ADDED
img_example/zidane.jpg ADDED
main.py ADDED
@@ -0,0 +1,1040 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Here are some use cases:
3
+ python main.py --config config/all.yaml --experiment experiment_8x1 --signature demo1 --target data/demo1.png
4
+ """
5
+ import pydiffvg
6
+ import torch
7
+ import cv2
8
+ import matplotlib.pyplot as plt
9
+ import random
10
+ import argparse
11
+ import math
12
+ import errno
13
+ from tqdm import tqdm
14
+ from torch.optim.lr_scheduler import CosineAnnealingLR, LambdaLR
15
+ from torch.nn.functional import adaptive_avg_pool2d
16
+ import warnings
17
+ warnings.filterwarnings("ignore")
18
+
19
+ import PIL
20
+ import PIL.Image
21
+ import os
22
+ import os.path as osp
23
+ import numpy as np
24
+ import numpy.random as npr
25
+ import shutil
26
+ import copy
27
+ # import skfmm
28
+ from xing_loss import xing_loss
29
+
30
+ import yaml
31
+ from easydict import EasyDict as edict
32
+
33
+
34
+ pydiffvg.set_print_timing(False)
35
+ gamma = 1.0
36
+
37
+ ##########
38
+ # helper #
39
+ ##########
40
+
41
+ from utils import \
42
+ get_experiment_id, \
43
+ get_path_schedule, \
44
+ edict_2_dict, \
45
+ check_and_create_dir
46
+
47
+ def get_bezier_circle(radius=1, segments=4, bias=None):
48
+ points = []
49
+ if bias is None:
50
+ bias = (random.random(), random.random())
51
+ avg_degree = 360 / (segments*3)
52
+ for i in range(0, segments*3):
53
+ point = (np.cos(np.deg2rad(i * avg_degree)),
54
+ np.sin(np.deg2rad(i * avg_degree)))
55
+ points.append(point)
56
+ points = torch.tensor(points)
57
+ points = (points)*radius + torch.tensor(bias).unsqueeze(dim=0)
58
+ points = points.type(torch.FloatTensor)
59
+ return points
60
+
61
+ def get_sdf(phi, method='skfmm', **kwargs):
62
+ if method == 'skfmm':
63
+ import skfmm
64
+ phi = (phi-0.5)*2
65
+ if (phi.max() <= 0) or (phi.min() >= 0):
66
+ return np.zeros(phi.shape).astype(np.float32)
67
+ sd = skfmm.distance(phi, dx=1)
68
+
69
+ flip_negative = kwargs.get('flip_negative', True)
70
+ if flip_negative:
71
+ sd = np.abs(sd)
72
+
73
+ truncate = kwargs.get('truncate', 10)
74
+ sd = np.clip(sd, -truncate, truncate)
75
+ # print(f"max sd value is: {sd.max()}")
76
+
77
+ zero2max = kwargs.get('zero2max', True)
78
+ if zero2max and flip_negative:
79
+ sd = sd.max() - sd
80
+ elif zero2max:
81
+ raise ValueError
82
+
83
+ normalize = kwargs.get('normalize', 'sum')
84
+ if normalize == 'sum':
85
+ sd /= sd.sum()
86
+ elif normalize == 'to1':
87
+ sd /= sd.max()
88
+ return sd
89
+
90
+ def parse_args():
91
+ parser = argparse.ArgumentParser()
92
+ parser.add_argument('--debug', action='store_true', default=False)
93
+ parser.add_argument("--config", type=str)
94
+ parser.add_argument("--experiment", type=str)
95
+ parser.add_argument("--seed", type=int)
96
+ parser.add_argument("--target", type=str, help="target image path")
97
+ parser.add_argument('--log_dir', metavar='DIR', default="log/debug")
98
+ parser.add_argument('--initial', type=str, default="random", choices=['random', 'circle'])
99
+ parser.add_argument('--signature', nargs='+', type=str)
100
+ parser.add_argument('--seginit', nargs='+', type=str)
101
+ parser.add_argument("--num_segments", type=int, default=4)
102
+ # parser.add_argument("--num_paths", type=str, default="1,1,1")
103
+ # parser.add_argument("--num_iter", type=int, default=500)
104
+ # parser.add_argument('--free', action='store_true')
105
+ # Please ensure that image resolution is divisible by pool_size; otherwise the performance would drop a lot.
106
+ # parser.add_argument('--pool_size', type=int, default=40, help="the pooled image size for next path initialization")
107
+ # parser.add_argument('--save_loss', action='store_true')
108
+ # parser.add_argument('--save_init', action='store_true')
109
+ # parser.add_argument('--save_image', action='store_true')
110
+ # parser.add_argument('--save_video', action='store_true')
111
+ # parser.add_argument('--print_weight', action='store_true')
112
+ # parser.add_argument('--circle_init_radius', type=float)
113
+ cfg = edict()
114
+ args = parser.parse_args()
115
+ cfg.debug = args.debug
116
+ cfg.config = args.config
117
+ cfg.experiment = args.experiment
118
+ cfg.seed = args.seed
119
+ cfg.target = args.target
120
+ cfg.log_dir = args.log_dir
121
+ cfg.initial = args.initial
122
+ cfg.signature = args.signature
123
+ # set cfg num_segments in command
124
+ cfg.num_segments = args.num_segments
125
+ if args.seginit is not None:
126
+ cfg.seginit = edict()
127
+ cfg.seginit.type = args.seginit[0]
128
+ if cfg.seginit.type == 'circle':
129
+ cfg.seginit.radius = float(args.seginit[1])
130
+ return cfg
131
+
132
+ def ycrcb_conversion(im, format='[bs x 3 x 2D]', reverse=False):
133
+ mat = torch.FloatTensor([
134
+ [ 65.481/255, 128.553/255, 24.966/255], # ranged_from [0, 219/255]
135
+ [-37.797/255, -74.203/255, 112.000/255], # ranged_from [-112/255, 112/255]
136
+ [112.000/255, -93.786/255, -18.214/255], # ranged_from [-112/255, 112/255]
137
+ ]).to(im.device)
138
+
139
+ if reverse:
140
+ mat = mat.inverse()
141
+
142
+ if format == '[bs x 3 x 2D]':
143
+ im = im.permute(0, 2, 3, 1)
144
+ im = torch.matmul(im, mat.T)
145
+ im = im.permute(0, 3, 1, 2).contiguous()
146
+ return im
147
+ elif format == '[2D x 3]':
148
+ im = torch.matmul(im, mat.T)
149
+ return im
150
+ else:
151
+ raise ValueError
152
+
153
+ class random_coord_init():
154
+ def __init__(self, canvas_size):
155
+ self.canvas_size = canvas_size
156
+ def __call__(self):
157
+ h, w = self.canvas_size
158
+ return [npr.uniform(0, 1)*w, npr.uniform(0, 1)*h]
159
+
160
+ class naive_coord_init():
161
+ def __init__(self, pred, gt, format='[bs x c x 2D]', replace_sampling=True):
162
+ if isinstance(pred, torch.Tensor):
163
+ pred = pred.detach().cpu().numpy()
164
+ if isinstance(gt, torch.Tensor):
165
+ gt = gt.detach().cpu().numpy()
166
+
167
+ if format == '[bs x c x 2D]':
168
+ self.map = ((pred[0] - gt[0])**2).sum(0)
169
+ elif format == ['[2D x c]']:
170
+ self.map = ((pred - gt)**2).sum(-1)
171
+ else:
172
+ raise ValueError
173
+ self.replace_sampling = replace_sampling
174
+
175
+ def __call__(self):
176
+ coord = np.where(self.map == self.map.max())
177
+ coord_h, coord_w = coord[0][0], coord[1][0]
178
+ if self.replace_sampling:
179
+ self.map[coord_h, coord_w] = -1
180
+ return [coord_w, coord_h]
181
+
182
+
183
+ class sparse_coord_init():
184
+ def __init__(self, pred, gt, format='[bs x c x 2D]', quantile_interval=200, nodiff_thres=0.1):
185
+ if isinstance(pred, torch.Tensor):
186
+ pred = pred.detach().cpu().numpy()
187
+ if isinstance(gt, torch.Tensor):
188
+ gt = gt.detach().cpu().numpy()
189
+ if format == '[bs x c x 2D]':
190
+ self.map = ((pred[0] - gt[0])**2).sum(0)
191
+ self.reference_gt = copy.deepcopy(
192
+ np.transpose(gt[0], (1, 2, 0)))
193
+ elif format == ['[2D x c]']:
194
+ self.map = (np.abs(pred - gt)).sum(-1)
195
+ self.reference_gt = copy.deepcopy(gt[0])
196
+ else:
197
+ raise ValueError
198
+ # OptionA: Zero too small errors to avoid the error too small deadloop
199
+ self.map[self.map < nodiff_thres] = 0
200
+ quantile_interval = np.linspace(0., 1., quantile_interval)
201
+ quantized_interval = np.quantile(self.map, quantile_interval)
202
+ # remove redundant
203
+ quantized_interval = np.unique(quantized_interval)
204
+ quantized_interval = sorted(quantized_interval[1:-1])
205
+ self.map = np.digitize(self.map, quantized_interval, right=False)
206
+ self.map = np.clip(self.map, 0, 255).astype(np.uint8)
207
+ self.idcnt = {}
208
+ for idi in sorted(np.unique(self.map)):
209
+ self.idcnt[idi] = (self.map==idi).sum()
210
+ self.idcnt.pop(min(self.idcnt.keys()))
211
+ # remove smallest one to remove the correct region
212
+ def __call__(self):
213
+ if len(self.idcnt) == 0:
214
+ h, w = self.map.shape
215
+ return [npr.uniform(0, 1)*w, npr.uniform(0, 1)*h]
216
+ target_id = max(self.idcnt, key=self.idcnt.get)
217
+ _, component, cstats, ccenter = cv2.connectedComponentsWithStats(
218
+ (self.map==target_id).astype(np.uint8), connectivity=4)
219
+ # remove cid = 0, it is the invalid area
220
+ csize = [ci[-1] for ci in cstats[1:]]
221
+ target_cid = csize.index(max(csize))+1
222
+ center = ccenter[target_cid][::-1]
223
+ coord = np.stack(np.where(component == target_cid)).T
224
+ dist = np.linalg.norm(coord-center, axis=1)
225
+ target_coord_id = np.argmin(dist)
226
+ coord_h, coord_w = coord[target_coord_id]
227
+ # replace_sampling
228
+ self.idcnt[target_id] -= max(csize)
229
+ if self.idcnt[target_id] == 0:
230
+ self.idcnt.pop(target_id)
231
+ self.map[component == target_cid] = 0
232
+ return [coord_w, coord_h]
233
+
234
+
235
+ def init_shapes(num_paths,
236
+ num_segments,
237
+ canvas_size,
238
+ seginit_cfg,
239
+ shape_cnt,
240
+ pos_init_method=None,
241
+ trainable_stroke=False,
242
+ gt=None,
243
+ **kwargs):
244
+ shapes = []
245
+ shape_groups = []
246
+ h, w = canvas_size
247
+
248
+ # change path init location
249
+ if pos_init_method is None:
250
+ pos_init_method = random_coord_init(canvas_size=canvas_size)
251
+
252
+ for i in range(num_paths):
253
+ num_control_points = [2] * num_segments
254
+
255
+ if seginit_cfg.type=="random":
256
+ points = []
257
+ p0 = pos_init_method()
258
+ color_ref = copy.deepcopy(p0)
259
+ points.append(p0)
260
+ for j in range(num_segments):
261
+ radius = seginit_cfg.radius
262
+ p1 = (p0[0] + radius * npr.uniform(-0.5, 0.5),
263
+ p0[1] + radius * npr.uniform(-0.5, 0.5))
264
+ p2 = (p1[0] + radius * npr.uniform(-0.5, 0.5),
265
+ p1[1] + radius * npr.uniform(-0.5, 0.5))
266
+ p3 = (p2[0] + radius * npr.uniform(-0.5, 0.5),
267
+ p2[1] + radius * npr.uniform(-0.5, 0.5))
268
+ points.append(p1)
269
+ points.append(p2)
270
+ if j < num_segments - 1:
271
+ points.append(p3)
272
+ p0 = p3
273
+ points = torch.FloatTensor(points)
274
+
275
+ # circle points initialization
276
+ elif seginit_cfg.type=="circle":
277
+ radius = seginit_cfg.radius
278
+ if radius is None:
279
+ radius = npr.uniform(0.5, 1)
280
+ center = pos_init_method()
281
+ color_ref = copy.deepcopy(center)
282
+ points = get_bezier_circle(
283
+ radius=radius, segments=num_segments,
284
+ bias=center)
285
+
286
+ path = pydiffvg.Path(num_control_points = torch.LongTensor(num_control_points),
287
+ points = points,
288
+ stroke_width = torch.tensor(0.0),
289
+ is_closed = True)
290
+ shapes.append(path)
291
+ # !!!!!!problem is here. the shape group shape_ids is wrong
292
+
293
+ if gt is not None:
294
+ wref, href = color_ref
295
+ wref = max(0, min(int(wref), w-1))
296
+ href = max(0, min(int(href), h-1))
297
+ fill_color_init = list(gt[0, :, href, wref]) + [1.]
298
+ fill_color_init = torch.FloatTensor(fill_color_init)
299
+ stroke_color_init = torch.FloatTensor(npr.uniform(size=[4]))
300
+ else:
301
+ fill_color_init = torch.FloatTensor(npr.uniform(size=[4]))
302
+ stroke_color_init = torch.FloatTensor(npr.uniform(size=[4]))
303
+
304
+ path_group = pydiffvg.ShapeGroup(
305
+ shape_ids = torch.LongTensor([shape_cnt+i]),
306
+ fill_color = fill_color_init,
307
+ stroke_color = stroke_color_init,
308
+ )
309
+ shape_groups.append(path_group)
310
+
311
+ point_var = []
312
+ color_var = []
313
+
314
+ for path in shapes:
315
+ path.points.requires_grad = True
316
+ point_var.append(path.points)
317
+ for group in shape_groups:
318
+ group.fill_color.requires_grad = True
319
+ color_var.append(group.fill_color)
320
+
321
+ if trainable_stroke:
322
+ stroke_width_var = []
323
+ stroke_color_var = []
324
+ for path in shapes:
325
+ path.stroke_width.requires_grad = True
326
+ stroke_width_var.append(path.stroke_width)
327
+ for group in shape_groups:
328
+ group.stroke_color.requires_grad = True
329
+ stroke_color_var.append(group.stroke_color)
330
+ return shapes, shape_groups, point_var, color_var, stroke_width_var, stroke_color_var
331
+ else:
332
+ return shapes, shape_groups, point_var, color_var
333
+
334
+ class linear_decay_lrlambda_f(object):
335
+ def __init__(self, decay_every, decay_ratio):
336
+ self.decay_every = decay_every
337
+ self.decay_ratio = decay_ratio
338
+
339
+ def __call__(self, n):
340
+ decay_time = n//self.decay_every
341
+ decay_step = n %self.decay_every
342
+ lr_s = self.decay_ratio**decay_time
343
+ lr_e = self.decay_ratio**(decay_time+1)
344
+ r = decay_step/self.decay_every
345
+ lr = lr_s * (1-r) + lr_e * r
346
+ return lr
347
+
348
+ def main_func(target, experiment, num_iter, cfg_arg):
349
+ with open(cfg_arg.config, 'r') as f:
350
+ cfg = yaml.load(f, Loader=yaml.FullLoader)
351
+ cfg_default = edict(cfg['default'])
352
+ cfg = edict(cfg[cfg_arg.experiment])
353
+ cfg.update(cfg_default)
354
+ cfg.update(cfg_arg)
355
+ cfg.exid = get_experiment_id(cfg.debug)
356
+
357
+ cfg.experiment_dir = \
358
+ osp.join(cfg.log_dir, '{}_{}'.format(cfg.exid, '_'.join(cfg.signature)))
359
+ cfg.target = target
360
+ cfg.experiment = experiment
361
+ cfg.num_iter = num_iter
362
+
363
+ configfile = osp.join(cfg.experiment_dir, 'config.yaml')
364
+ check_and_create_dir(configfile)
365
+ with open(osp.join(configfile), 'w') as f:
366
+ yaml.dump(edict_2_dict(cfg), f)
367
+
368
+ # Use GPU if available
369
+ pydiffvg.set_use_gpu(torch.cuda.is_available())
370
+ device = pydiffvg.get_device()
371
+
372
+ # gt = np.array(PIL.Image.open(cfg.target))
373
+ gt = np.array(cfg.target)
374
+ print(f"Input image shape is: {gt.shape}")
375
+ if len(gt.shape) == 2:
376
+ print("Converting the gray-scale image to RGB.")
377
+ gt = gt.unsqueeze(dim=-1).repeat(1,1,3)
378
+ if gt.shape[2] == 4:
379
+ print("Input image includes alpha channel, simply dropout alpha channel.")
380
+ gt = gt[:, :, :3]
381
+ gt = (gt/255).astype(np.float32)
382
+ gt = torch.FloatTensor(gt).permute(2, 0, 1)[None].to(device)
383
+ if cfg.use_ycrcb:
384
+ gt = ycrcb_conversion(gt)
385
+ h, w = gt.shape[2:]
386
+
387
+ path_schedule = get_path_schedule(**cfg.path_schedule)
388
+
389
+ if cfg.seed is not None:
390
+ random.seed(cfg.seed)
391
+ npr.seed(cfg.seed)
392
+ torch.manual_seed(cfg.seed)
393
+ render = pydiffvg.RenderFunction.apply
394
+
395
+ shapes_record, shape_groups_record = [], []
396
+
397
+ region_loss = None
398
+ loss_matrix = []
399
+
400
+ para_point, para_color = {}, {}
401
+ if cfg.trainable.stroke:
402
+ para_stroke_width, para_stroke_color = {}, {}
403
+
404
+ pathn_record = []
405
+ # Background
406
+ if cfg.trainable.bg:
407
+ # meancolor = gt.mean([2, 3])[0]
408
+ para_bg = torch.tensor([1., 1., 1.], requires_grad=True, device=device)
409
+ else:
410
+ if cfg.use_ycrcb:
411
+ para_bg = torch.tensor([219/255, 0, 0], requires_grad=False, device=device)
412
+ else:
413
+ para_bg = torch.tensor([1., 1., 1.], requires_grad=False, device=device)
414
+
415
+ ##################
416
+ # start_training #
417
+ ##################
418
+
419
+ loss_weight = None
420
+ loss_weight_keep = 0
421
+ if cfg.coord_init.type == 'naive':
422
+ pos_init_method = naive_coord_init(
423
+ para_bg.view(1, -1, 1, 1).repeat(1, 1, h, w), gt)
424
+ elif cfg.coord_init.type == 'sparse':
425
+ pos_init_method = sparse_coord_init(
426
+ para_bg.view(1, -1, 1, 1).repeat(1, 1, h, w), gt)
427
+ elif cfg.coord_init.type == 'random':
428
+ pos_init_method = random_coord_init([h, w])
429
+ else:
430
+ raise ValueError
431
+
432
+ lrlambda_f = linear_decay_lrlambda_f(cfg.num_iter, 0.4)
433
+ optim_schedular_dict = {}
434
+
435
+ for path_idx, pathn in enumerate(path_schedule):
436
+ loss_list = []
437
+ print("=> Adding [{}] paths, [{}] ...".format(pathn, cfg.seginit.type))
438
+ pathn_record.append(pathn)
439
+ pathn_record_str = '-'.join([str(i) for i in pathn_record])
440
+
441
+ # initialize new shapes related stuffs.
442
+ if cfg.trainable.stroke:
443
+ shapes, shape_groups, point_var, color_var, stroke_width_var, stroke_color_var = init_shapes(
444
+ pathn, cfg.num_segments, (h, w),
445
+ cfg.seginit, len(shapes_record),
446
+ pos_init_method,
447
+ trainable_stroke=True,
448
+ gt=gt, )
449
+ para_stroke_width[path_idx] = stroke_width_var
450
+ para_stroke_color[path_idx] = stroke_color_var
451
+ else:
452
+ shapes, shape_groups, point_var, color_var = init_shapes(
453
+ pathn, cfg.num_segments, (h, w),
454
+ cfg.seginit, len(shapes_record),
455
+ pos_init_method,
456
+ trainable_stroke=False,
457
+ gt=gt, )
458
+
459
+ shapes_record += shapes
460
+ shape_groups_record += shape_groups
461
+
462
+ if cfg.save.init:
463
+ filename = os.path.join(
464
+ cfg.experiment_dir, "svg-init",
465
+ "{}-init.svg".format(pathn_record_str))
466
+ check_and_create_dir(filename)
467
+ pydiffvg.save_svg(
468
+ filename, w, h,
469
+ shapes_record, shape_groups_record)
470
+
471
+ para = {}
472
+ if (cfg.trainable.bg) and (path_idx == 0):
473
+ para['bg'] = [para_bg]
474
+ para['point'] = point_var
475
+ para['color'] = color_var
476
+ if cfg.trainable.stroke:
477
+ para['stroke_width'] = stroke_width_var
478
+ para['stroke_color'] = stroke_color_var
479
+
480
+ pg = [{'params' : para[ki], 'lr' : cfg.lr_base[ki]} for ki in sorted(para.keys())]
481
+ optim = torch.optim.Adam(pg)
482
+
483
+ if cfg.trainable.record:
484
+ scheduler = LambdaLR(
485
+ optim, lr_lambda=lrlambda_f, last_epoch=-1)
486
+ else:
487
+ scheduler = LambdaLR(
488
+ optim, lr_lambda=lrlambda_f, last_epoch=cfg.num_iter)
489
+ optim_schedular_dict[path_idx] = (optim, scheduler)
490
+
491
+ # Inner loop training
492
+ t_range = tqdm(range(cfg.num_iter))
493
+ for t in t_range:
494
+
495
+ for _, (optim, _) in optim_schedular_dict.items():
496
+ optim.zero_grad()
497
+
498
+ # Forward pass: render the image.
499
+ scene_args = pydiffvg.RenderFunction.serialize_scene(
500
+ w, h, shapes_record, shape_groups_record)
501
+ img = render(w, h, 2, 2, t, None, *scene_args)
502
+
503
+ # Compose img with white background
504
+ img = img[:, :, 3:4] * img[:, :, :3] + \
505
+ para_bg * (1 - img[:, :, 3:4])
506
+
507
+
508
+
509
+
510
+
511
+ if cfg.save.video:
512
+ filename = os.path.join(
513
+ cfg.experiment_dir, "video-png",
514
+ "{}-iter{}.png".format(pathn_record_str, t))
515
+ check_and_create_dir(filename)
516
+ if cfg.use_ycrcb:
517
+ imshow = ycrcb_conversion(
518
+ img, format='[2D x 3]', reverse=True).detach().cpu()
519
+ else:
520
+ imshow = img.detach().cpu()
521
+ pydiffvg.imwrite(imshow, filename, gamma=gamma)
522
+
523
+ # ### added for app
524
+ # if t%30==0 and t !=0 :
525
+ # # print(f"debug: {t}, {filename} {img.size()}")
526
+ # return img.detach().cpu().numpy(), t
527
+
528
+ x = img.unsqueeze(0).permute(0, 3, 1, 2) # HWC -> NCHW
529
+
530
+ if cfg.use_ycrcb:
531
+ color_reweight = torch.FloatTensor([255/219, 255/224, 255/255]).to(device)
532
+ loss = ((x-gt)*(color_reweight.view(1, -1, 1, 1)))**2
533
+ else:
534
+ loss = ((x-gt)**2)
535
+
536
+ if cfg.loss.use_l1_loss:
537
+ loss = abs(x-gt)
538
+
539
+ if cfg.loss.use_distance_weighted_loss:
540
+ if cfg.use_ycrcb:
541
+ raise ValueError
542
+ shapes_forsdf = copy.deepcopy(shapes)
543
+ shape_groups_forsdf = copy.deepcopy(shape_groups)
544
+ for si in shapes_forsdf:
545
+ si.stroke_width = torch.FloatTensor([0]).to(device)
546
+ for sg_idx, sgi in enumerate(shape_groups_forsdf):
547
+ sgi.fill_color = torch.FloatTensor([1, 1, 1, 1]).to(device)
548
+ sgi.shape_ids = torch.LongTensor([sg_idx]).to(device)
549
+
550
+ sargs_forsdf = pydiffvg.RenderFunction.serialize_scene(
551
+ w, h, shapes_forsdf, shape_groups_forsdf)
552
+ with torch.no_grad():
553
+ im_forsdf = render(w, h, 2, 2, 0, None, *sargs_forsdf)
554
+ # use alpha channel is a trick to get 0-1 image
555
+ im_forsdf = (im_forsdf[:, :, 3]).detach().cpu().numpy()
556
+ loss_weight = get_sdf(im_forsdf, normalize='to1')
557
+ loss_weight += loss_weight_keep
558
+ loss_weight = np.clip(loss_weight, 0, 1)
559
+ loss_weight = torch.FloatTensor(loss_weight).to(device)
560
+
561
+ if cfg.save.loss:
562
+ save_loss = loss.squeeze(dim=0).mean(dim=0,keepdim=False).cpu().detach().numpy()
563
+ save_weight = loss_weight.cpu().detach().numpy()
564
+ save_weighted_loss = save_loss*save_weight
565
+ # normalize to [0,1]
566
+ save_loss = (save_loss - np.min(save_loss))/np.ptp(save_loss)
567
+ save_weight = (save_weight - np.min(save_weight))/np.ptp(save_weight)
568
+ save_weighted_loss = (save_weighted_loss - np.min(save_weighted_loss))/np.ptp(save_weighted_loss)
569
+
570
+ # save
571
+ plt.imshow(save_loss, cmap='Reds')
572
+ plt.axis('off')
573
+ # plt.colorbar()
574
+ filename = os.path.join(cfg.experiment_dir, "loss", "{}-iter{}-mseloss.png".format(pathn_record_str, t))
575
+ check_and_create_dir(filename)
576
+ plt.savefig(filename, dpi=800)
577
+ plt.close()
578
+
579
+ plt.imshow(save_weight, cmap='Greys')
580
+ plt.axis('off')
581
+ # plt.colorbar()
582
+ filename = os.path.join(cfg.experiment_dir, "loss", "{}-iter{}-sdfweight.png".format(pathn_record_str, t))
583
+ plt.savefig(filename, dpi=800)
584
+ plt.close()
585
+
586
+ plt.imshow(save_weighted_loss, cmap='Reds')
587
+ plt.axis('off')
588
+ # plt.colorbar()
589
+ filename = os.path.join(cfg.experiment_dir, "loss", "{}-iter{}-weightedloss.png".format(pathn_record_str, t))
590
+ plt.savefig(filename, dpi=800)
591
+ plt.close()
592
+
593
+
594
+
595
+
596
+
597
+ if loss_weight is None:
598
+ loss = loss.sum(1).mean()
599
+ else:
600
+ loss = (loss.sum(1)*loss_weight).mean()
601
+
602
+ # if (cfg.loss.bis_loss_weight is not None) and (cfg.loss.bis_loss_weight > 0):
603
+ # loss_bis = bezier_intersection_loss(point_var[0]) * cfg.loss.bis_loss_weight
604
+ # loss = loss + loss_bis
605
+ if (cfg.loss.xing_loss_weight is not None) \
606
+ and (cfg.loss.xing_loss_weight > 0):
607
+ loss_xing = xing_loss(point_var) * cfg.loss.xing_loss_weight
608
+ loss = loss + loss_xing
609
+
610
+
611
+ loss_list.append(loss.item())
612
+ t_range.set_postfix({'loss': loss.item()})
613
+ loss.backward()
614
+
615
+ # step
616
+ for _, (optim, scheduler) in optim_schedular_dict.items():
617
+ optim.step()
618
+ scheduler.step()
619
+
620
+ for group in shape_groups_record:
621
+ group.fill_color.data.clamp_(0.0, 1.0)
622
+
623
+ if cfg.loss.use_distance_weighted_loss:
624
+ loss_weight_keep = loss_weight.detach().cpu().numpy() * 1
625
+
626
+ if not cfg.trainable.record:
627
+ for _, pi in pg.items():
628
+ for ppi in pi:
629
+ pi.require_grad = False
630
+ optim_schedular_dict = {}
631
+
632
+ if cfg.save.image:
633
+ filename = os.path.join(
634
+ cfg.experiment_dir, "demo-png", "{}.png".format(pathn_record_str))
635
+ check_and_create_dir(filename)
636
+ if cfg.use_ycrcb:
637
+ imshow = ycrcb_conversion(
638
+ img, format='[2D x 3]', reverse=True).detach().cpu()
639
+ else:
640
+ imshow = img.detach().cpu()
641
+ pydiffvg.imwrite(imshow, filename, gamma=gamma)
642
+
643
+ svg_app_file_name = ""
644
+ if cfg.save.output:
645
+ filename = os.path.join(
646
+ cfg.experiment_dir, "output-svg", "{}.svg".format(pathn_record_str))
647
+ check_and_create_dir(filename)
648
+ pydiffvg.save_svg(filename, w, h, shapes_record, shape_groups_record)
649
+ svg_app_file_name = filename
650
+
651
+ loss_matrix.append(loss_list)
652
+
653
+ # calculate the pixel loss
654
+ # pixel_loss = ((x-gt)**2).sum(dim=1, keepdim=True).sqrt_() # [N,1,H, W]
655
+ # region_loss = adaptive_avg_pool2d(pixel_loss, cfg.region_loss_pool_size)
656
+ # loss_weight = torch.softmax(region_loss.reshape(1, 1, -1), dim=-1)\
657
+ # .reshape_as(region_loss)
658
+
659
+ pos_init_method = naive_coord_init(x, gt)
660
+
661
+ if cfg.coord_init.type == 'naive':
662
+ pos_init_method = naive_coord_init(x, gt)
663
+ elif cfg.coord_init.type == 'sparse':
664
+ pos_init_method = sparse_coord_init(x, gt)
665
+ elif cfg.coord_init.type == 'random':
666
+ pos_init_method = random_coord_init([h, w])
667
+ else:
668
+ raise ValueError
669
+
670
+ if cfg.save.video:
671
+ print("saving iteration video...")
672
+ img_array = []
673
+ for ii in range(0, cfg.num_iter):
674
+ filename = os.path.join(
675
+ cfg.experiment_dir, "video-png",
676
+ "{}-iter{}.png".format(pathn_record_str, ii))
677
+ img = cv2.imread(filename)
678
+ # cv2.putText(
679
+ # img, "Path:{} \nIteration:{}".format(pathn_record_str, ii),
680
+ # (10, 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 1)
681
+ img_array.append(img)
682
+
683
+ videoname = os.path.join(
684
+ cfg.experiment_dir, "video-avi",
685
+ "{}.avi".format(pathn_record_str))
686
+ check_and_create_dir(videoname)
687
+ out = cv2.VideoWriter(
688
+ videoname,
689
+ # cv2.VideoWriter_fourcc(*'mp4v'),
690
+ cv2.VideoWriter_fourcc(*'FFV1'),
691
+ 20.0, (w, h))
692
+ for iii in range(len(img_array)):
693
+ out.write(img_array[iii])
694
+ out.release()
695
+ # shutil.rmtree(os.path.join(cfg.experiment_dir, "video-png"))
696
+
697
+ print("The last loss is: {}".format(loss.item()))
698
+ return img.detach().cpu().numpy(), svg_app_file_name
699
+
700
+
701
+ if __name__ == "__main__":
702
+
703
+ ###############
704
+ # make config #
705
+ ###############
706
+
707
+ cfg_arg = parse_args()
708
+ with open(cfg_arg.config, 'r') as f:
709
+ cfg = yaml.load(f, Loader=yaml.FullLoader)
710
+ cfg_default = edict(cfg['default'])
711
+ cfg = edict(cfg[cfg_arg.experiment])
712
+ cfg.update(cfg_default)
713
+ cfg.update(cfg_arg)
714
+ cfg.exid = get_experiment_id(cfg.debug)
715
+
716
+ cfg.experiment_dir = \
717
+ osp.join(cfg.log_dir, '{}_{}'.format(cfg.exid, '_'.join(cfg.signature)))
718
+ configfile = osp.join(cfg.experiment_dir, 'config.yaml')
719
+ check_and_create_dir(configfile)
720
+ with open(osp.join(configfile), 'w') as f:
721
+ yaml.dump(edict_2_dict(cfg), f)
722
+
723
+ # Use GPU if available
724
+ pydiffvg.set_use_gpu(torch.cuda.is_available())
725
+ device = pydiffvg.get_device()
726
+
727
+ gt = np.array(PIL.Image.open(cfg.target))
728
+ print(f"Input image shape is: {gt.shape}")
729
+ if len(gt.shape) == 2:
730
+ print("Converting the gray-scale image to RGB.")
731
+ gt = gt.unsqueeze(dim=-1).repeat(1,1,3)
732
+ if gt.shape[2] == 4:
733
+ print("Input image includes alpha channel, simply dropout alpha channel.")
734
+ gt = gt[:, :, :3]
735
+ gt = (gt/255).astype(np.float32)
736
+ gt = torch.FloatTensor(gt).permute(2, 0, 1)[None].to(device)
737
+ if cfg.use_ycrcb:
738
+ gt = ycrcb_conversion(gt)
739
+ h, w = gt.shape[2:]
740
+
741
+ path_schedule = get_path_schedule(**cfg.path_schedule)
742
+
743
+ if cfg.seed is not None:
744
+ random.seed(cfg.seed)
745
+ npr.seed(cfg.seed)
746
+ torch.manual_seed(cfg.seed)
747
+ render = pydiffvg.RenderFunction.apply
748
+
749
+ shapes_record, shape_groups_record = [], []
750
+
751
+ region_loss = None
752
+ loss_matrix = []
753
+
754
+ para_point, para_color = {}, {}
755
+ if cfg.trainable.stroke:
756
+ para_stroke_width, para_stroke_color = {}, {}
757
+
758
+ pathn_record = []
759
+ # Background
760
+ if cfg.trainable.bg:
761
+ # meancolor = gt.mean([2, 3])[0]
762
+ para_bg = torch.tensor([1., 1., 1.], requires_grad=True, device=device)
763
+ else:
764
+ if cfg.use_ycrcb:
765
+ para_bg = torch.tensor([219/255, 0, 0], requires_grad=False, device=device)
766
+ else:
767
+ para_bg = torch.tensor([1., 1., 1.], requires_grad=False, device=device)
768
+
769
+ ##################
770
+ # start_training #
771
+ ##################
772
+
773
+ loss_weight = None
774
+ loss_weight_keep = 0
775
+ if cfg.coord_init.type == 'naive':
776
+ pos_init_method = naive_coord_init(
777
+ para_bg.view(1, -1, 1, 1).repeat(1, 1, h, w), gt)
778
+ elif cfg.coord_init.type == 'sparse':
779
+ pos_init_method = sparse_coord_init(
780
+ para_bg.view(1, -1, 1, 1).repeat(1, 1, h, w), gt)
781
+ elif cfg.coord_init.type == 'random':
782
+ pos_init_method = random_coord_init([h, w])
783
+ else:
784
+ raise ValueError
785
+
786
+ lrlambda_f = linear_decay_lrlambda_f(cfg.num_iter, 0.4)
787
+ optim_schedular_dict = {}
788
+
789
+ for path_idx, pathn in enumerate(path_schedule):
790
+ loss_list = []
791
+ print("=> Adding [{}] paths, [{}] ...".format(pathn, cfg.seginit.type))
792
+ pathn_record.append(pathn)
793
+ pathn_record_str = '-'.join([str(i) for i in pathn_record])
794
+
795
+ # initialize new shapes related stuffs.
796
+ if cfg.trainable.stroke:
797
+ shapes, shape_groups, point_var, color_var, stroke_width_var, stroke_color_var = init_shapes(
798
+ pathn, cfg.num_segments, (h, w),
799
+ cfg.seginit, len(shapes_record),
800
+ pos_init_method,
801
+ trainable_stroke=True,
802
+ gt=gt, )
803
+ para_stroke_width[path_idx] = stroke_width_var
804
+ para_stroke_color[path_idx] = stroke_color_var
805
+ else:
806
+ shapes, shape_groups, point_var, color_var = init_shapes(
807
+ pathn, cfg.num_segments, (h, w),
808
+ cfg.seginit, len(shapes_record),
809
+ pos_init_method,
810
+ trainable_stroke=False,
811
+ gt=gt, )
812
+
813
+ shapes_record += shapes
814
+ shape_groups_record += shape_groups
815
+
816
+ if cfg.save.init:
817
+ filename = os.path.join(
818
+ cfg.experiment_dir, "svg-init",
819
+ "{}-init.svg".format(pathn_record_str))
820
+ check_and_create_dir(filename)
821
+ pydiffvg.save_svg(
822
+ filename, w, h,
823
+ shapes_record, shape_groups_record)
824
+
825
+ para = {}
826
+ if (cfg.trainable.bg) and (path_idx == 0):
827
+ para['bg'] = [para_bg]
828
+ para['point'] = point_var
829
+ para['color'] = color_var
830
+ if cfg.trainable.stroke:
831
+ para['stroke_width'] = stroke_width_var
832
+ para['stroke_color'] = stroke_color_var
833
+
834
+ pg = [{'params' : para[ki], 'lr' : cfg.lr_base[ki]} for ki in sorted(para.keys())]
835
+ optim = torch.optim.Adam(pg)
836
+
837
+ if cfg.trainable.record:
838
+ scheduler = LambdaLR(
839
+ optim, lr_lambda=lrlambda_f, last_epoch=-1)
840
+ else:
841
+ scheduler = LambdaLR(
842
+ optim, lr_lambda=lrlambda_f, last_epoch=cfg.num_iter)
843
+ optim_schedular_dict[path_idx] = (optim, scheduler)
844
+
845
+ # Inner loop training
846
+ t_range = tqdm(range(cfg.num_iter))
847
+ for t in t_range:
848
+
849
+ for _, (optim, _) in optim_schedular_dict.items():
850
+ optim.zero_grad()
851
+
852
+ # Forward pass: render the image.
853
+ scene_args = pydiffvg.RenderFunction.serialize_scene(
854
+ w, h, shapes_record, shape_groups_record)
855
+ img = render(w, h, 2, 2, t, None, *scene_args)
856
+
857
+ # Compose img with white background
858
+ img = img[:, :, 3:4] * img[:, :, :3] + \
859
+ para_bg * (1 - img[:, :, 3:4])
860
+
861
+ if cfg.save.video:
862
+ filename = os.path.join(
863
+ cfg.experiment_dir, "video-png",
864
+ "{}-iter{}.png".format(pathn_record_str, t))
865
+ check_and_create_dir(filename)
866
+ if cfg.use_ycrcb:
867
+ imshow = ycrcb_conversion(
868
+ img, format='[2D x 3]', reverse=True).detach().cpu()
869
+ else:
870
+ imshow = img.detach().cpu()
871
+ pydiffvg.imwrite(imshow, filename, gamma=gamma)
872
+
873
+ x = img.unsqueeze(0).permute(0, 3, 1, 2) # HWC -> NCHW
874
+
875
+ if cfg.use_ycrcb:
876
+ color_reweight = torch.FloatTensor([255/219, 255/224, 255/255]).to(device)
877
+ loss = ((x-gt)*(color_reweight.view(1, -1, 1, 1)))**2
878
+ else:
879
+ loss = ((x-gt)**2)
880
+
881
+ if cfg.loss.use_l1_loss:
882
+ loss = abs(x-gt)
883
+
884
+ if cfg.loss.use_distance_weighted_loss:
885
+ if cfg.use_ycrcb:
886
+ raise ValueError
887
+ shapes_forsdf = copy.deepcopy(shapes)
888
+ shape_groups_forsdf = copy.deepcopy(shape_groups)
889
+ for si in shapes_forsdf:
890
+ si.stroke_width = torch.FloatTensor([0]).to(device)
891
+ for sg_idx, sgi in enumerate(shape_groups_forsdf):
892
+ sgi.fill_color = torch.FloatTensor([1, 1, 1, 1]).to(device)
893
+ sgi.shape_ids = torch.LongTensor([sg_idx]).to(device)
894
+
895
+ sargs_forsdf = pydiffvg.RenderFunction.serialize_scene(
896
+ w, h, shapes_forsdf, shape_groups_forsdf)
897
+ with torch.no_grad():
898
+ im_forsdf = render(w, h, 2, 2, 0, None, *sargs_forsdf)
899
+ # use alpha channel is a trick to get 0-1 image
900
+ im_forsdf = (im_forsdf[:, :, 3]).detach().cpu().numpy()
901
+ loss_weight = get_sdf(im_forsdf, normalize='to1')
902
+ loss_weight += loss_weight_keep
903
+ loss_weight = np.clip(loss_weight, 0, 1)
904
+ loss_weight = torch.FloatTensor(loss_weight).to(device)
905
+
906
+ if cfg.save.loss:
907
+ save_loss = loss.squeeze(dim=0).mean(dim=0,keepdim=False).cpu().detach().numpy()
908
+ save_weight = loss_weight.cpu().detach().numpy()
909
+ save_weighted_loss = save_loss*save_weight
910
+ # normalize to [0,1]
911
+ save_loss = (save_loss - np.min(save_loss))/np.ptp(save_loss)
912
+ save_weight = (save_weight - np.min(save_weight))/np.ptp(save_weight)
913
+ save_weighted_loss = (save_weighted_loss - np.min(save_weighted_loss))/np.ptp(save_weighted_loss)
914
+
915
+ # save
916
+ plt.imshow(save_loss, cmap='Reds')
917
+ plt.axis('off')
918
+ # plt.colorbar()
919
+ filename = os.path.join(cfg.experiment_dir, "loss", "{}-iter{}-mseloss.png".format(pathn_record_str, t))
920
+ check_and_create_dir(filename)
921
+ plt.savefig(filename, dpi=800)
922
+ plt.close()
923
+
924
+ plt.imshow(save_weight, cmap='Greys')
925
+ plt.axis('off')
926
+ # plt.colorbar()
927
+ filename = os.path.join(cfg.experiment_dir, "loss", "{}-iter{}-sdfweight.png".format(pathn_record_str, t))
928
+ plt.savefig(filename, dpi=800)
929
+ plt.close()
930
+
931
+ plt.imshow(save_weighted_loss, cmap='Reds')
932
+ plt.axis('off')
933
+ # plt.colorbar()
934
+ filename = os.path.join(cfg.experiment_dir, "loss", "{}-iter{}-weightedloss.png".format(pathn_record_str, t))
935
+ plt.savefig(filename, dpi=800)
936
+ plt.close()
937
+
938
+
939
+
940
+
941
+
942
+ if loss_weight is None:
943
+ loss = loss.sum(1).mean()
944
+ else:
945
+ loss = (loss.sum(1)*loss_weight).mean()
946
+
947
+ # if (cfg.loss.bis_loss_weight is not None) and (cfg.loss.bis_loss_weight > 0):
948
+ # loss_bis = bezier_intersection_loss(point_var[0]) * cfg.loss.bis_loss_weight
949
+ # loss = loss + loss_bis
950
+ if (cfg.loss.xing_loss_weight is not None) \
951
+ and (cfg.loss.xing_loss_weight > 0):
952
+ loss_xing = xing_loss(point_var) * cfg.loss.xing_loss_weight
953
+ loss = loss + loss_xing
954
+
955
+
956
+ loss_list.append(loss.item())
957
+ t_range.set_postfix({'loss': loss.item()})
958
+ loss.backward()
959
+
960
+ # step
961
+ for _, (optim, scheduler) in optim_schedular_dict.items():
962
+ optim.step()
963
+ scheduler.step()
964
+
965
+ for group in shape_groups_record:
966
+ group.fill_color.data.clamp_(0.0, 1.0)
967
+
968
+ if cfg.loss.use_distance_weighted_loss:
969
+ loss_weight_keep = loss_weight.detach().cpu().numpy() * 1
970
+
971
+ if not cfg.trainable.record:
972
+ for _, pi in pg.items():
973
+ for ppi in pi:
974
+ pi.require_grad = False
975
+ optim_schedular_dict = {}
976
+
977
+ if cfg.save.image:
978
+ filename = os.path.join(
979
+ cfg.experiment_dir, "demo-png", "{}.png".format(pathn_record_str))
980
+ check_and_create_dir(filename)
981
+ if cfg.use_ycrcb:
982
+ imshow = ycrcb_conversion(
983
+ img, format='[2D x 3]', reverse=True).detach().cpu()
984
+ else:
985
+ imshow = img.detach().cpu()
986
+ pydiffvg.imwrite(imshow, filename, gamma=gamma)
987
+
988
+ if cfg.save.output:
989
+ filename = os.path.join(
990
+ cfg.experiment_dir, "output-svg", "{}.svg".format(pathn_record_str))
991
+ check_and_create_dir(filename)
992
+ pydiffvg.save_svg(filename, w, h, shapes_record, shape_groups_record)
993
+
994
+ loss_matrix.append(loss_list)
995
+
996
+ # calculate the pixel loss
997
+ # pixel_loss = ((x-gt)**2).sum(dim=1, keepdim=True).sqrt_() # [N,1,H, W]
998
+ # region_loss = adaptive_avg_pool2d(pixel_loss, cfg.region_loss_pool_size)
999
+ # loss_weight = torch.softmax(region_loss.reshape(1, 1, -1), dim=-1)\
1000
+ # .reshape_as(region_loss)
1001
+
1002
+ pos_init_method = naive_coord_init(x, gt)
1003
+
1004
+ if cfg.coord_init.type == 'naive':
1005
+ pos_init_method = naive_coord_init(x, gt)
1006
+ elif cfg.coord_init.type == 'sparse':
1007
+ pos_init_method = sparse_coord_init(x, gt)
1008
+ elif cfg.coord_init.type == 'random':
1009
+ pos_init_method = random_coord_init([h, w])
1010
+ else:
1011
+ raise ValueError
1012
+
1013
+ if cfg.save.video:
1014
+ print("saving iteration video...")
1015
+ img_array = []
1016
+ for ii in range(0, cfg.num_iter):
1017
+ filename = os.path.join(
1018
+ cfg.experiment_dir, "video-png",
1019
+ "{}-iter{}.png".format(pathn_record_str, ii))
1020
+ img = cv2.imread(filename)
1021
+ # cv2.putText(
1022
+ # img, "Path:{} \nIteration:{}".format(pathn_record_str, ii),
1023
+ # (10, 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 1)
1024
+ img_array.append(img)
1025
+
1026
+ videoname = os.path.join(
1027
+ cfg.experiment_dir, "video-avi",
1028
+ "{}.avi".format(pathn_record_str))
1029
+ check_and_create_dir(videoname)
1030
+ out = cv2.VideoWriter(
1031
+ videoname,
1032
+ # cv2.VideoWriter_fourcc(*'mp4v'),
1033
+ cv2.VideoWriter_fourcc(*'FFV1'),
1034
+ 20.0, (w, h))
1035
+ for iii in range(len(img_array)):
1036
+ out.write(img_array[iii])
1037
+ out.release()
1038
+ # shutil.rmtree(os.path.join(cfg.experiment_dir, "video-png"))
1039
+
1040
+ print("The last loss is: {}".format(loss.item()))
matrix.h ADDED
@@ -0,0 +1,544 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #pragma once
2
+
3
+ #include "diffvg.h"
4
+ #include "vector.h"
5
+ #include <iostream>
6
+
7
+ template <typename T>
8
+ struct TMatrix3x3 {
9
+ DEVICE
10
+ TMatrix3x3() {
11
+ for (int i = 0; i < 3; i++) {
12
+ for (int j = 0; j < 3; j++) {
13
+ data[i][j] = T(0);
14
+ }
15
+ }
16
+ }
17
+
18
+ template <typename T2>
19
+ DEVICE
20
+ TMatrix3x3(T2 *arr) {
21
+ data[0][0] = arr[0];
22
+ data[0][1] = arr[1];
23
+ data[0][2] = arr[2];
24
+ data[1][0] = arr[3];
25
+ data[1][1] = arr[4];
26
+ data[1][2] = arr[5];
27
+ data[2][0] = arr[6];
28
+ data[2][1] = arr[7];
29
+ data[2][2] = arr[8];
30
+ }
31
+ DEVICE
32
+ TMatrix3x3(T v00, T v01, T v02,
33
+ T v10, T v11, T v12,
34
+ T v20, T v21, T v22) {
35
+ data[0][0] = v00;
36
+ data[0][1] = v01;
37
+ data[0][2] = v02;
38
+ data[1][0] = v10;
39
+ data[1][1] = v11;
40
+ data[1][2] = v12;
41
+ data[2][0] = v20;
42
+ data[2][1] = v21;
43
+ data[2][2] = v22;
44
+ }
45
+
46
+ DEVICE
47
+ const T& operator()(int i, int j) const {
48
+ return data[i][j];
49
+ }
50
+ DEVICE
51
+ T& operator()(int i, int j) {
52
+ return data[i][j];
53
+ }
54
+ DEVICE
55
+ static TMatrix3x3<T> identity() {
56
+ TMatrix3x3<T> m(1, 0, 0,
57
+ 0, 1, 0,
58
+ 0, 0, 1);
59
+ return m;
60
+ }
61
+
62
+ T data[3][3];
63
+ };
64
+
65
+ using Matrix3x3 = TMatrix3x3<Real>;
66
+ using Matrix3x3f = TMatrix3x3<float>;
67
+
68
+ template <typename T>
69
+ struct TMatrix4x4 {
70
+ DEVICE TMatrix4x4() {
71
+ for (int i = 0; i < 4; i++) {
72
+ for (int j = 0; j < 4; j++) {
73
+ data[i][j] = T(0);
74
+ }
75
+ }
76
+ }
77
+
78
+ template <typename T2>
79
+ DEVICE TMatrix4x4(const T2 *arr) {
80
+ for (int i = 0; i < 4; i++) {
81
+ for (int j = 0; j < 4; j++) {
82
+ data[i][j] = (T)arr[i * 4 + j];
83
+ }
84
+ }
85
+ }
86
+
87
+ template <typename T2>
88
+ DEVICE TMatrix4x4(const TMatrix4x4<T2> &m) {
89
+ for (int i = 0; i < 4; i++) {
90
+ for (int j = 0; j < 4; j++) {
91
+ data[i][j] = T(m.data[i][j]);
92
+ }
93
+ }
94
+ }
95
+
96
+ template <typename T2>
97
+ DEVICE TMatrix4x4(T2 v00, T2 v01, T2 v02, T2 v03,
98
+ T2 v10, T2 v11, T2 v12, T2 v13,
99
+ T2 v20, T2 v21, T2 v22, T2 v23,
100
+ T2 v30, T2 v31, T2 v32, T2 v33) {
101
+ data[0][0] = (T)v00;
102
+ data[0][1] = (T)v01;
103
+ data[0][2] = (T)v02;
104
+ data[0][3] = (T)v03;
105
+ data[1][0] = (T)v10;
106
+ data[1][1] = (T)v11;
107
+ data[1][2] = (T)v12;
108
+ data[1][3] = (T)v13;
109
+ data[2][0] = (T)v20;
110
+ data[2][1] = (T)v21;
111
+ data[2][2] = (T)v22;
112
+ data[2][3] = (T)v23;
113
+ data[3][0] = (T)v30;
114
+ data[3][1] = (T)v31;
115
+ data[3][2] = (T)v32;
116
+ data[3][3] = (T)v33;
117
+ }
118
+
119
+ DEVICE
120
+ const T& operator()(int i, int j) const {
121
+ return data[i][j];
122
+ }
123
+
124
+ DEVICE
125
+ T& operator()(int i, int j) {
126
+ return data[i][j];
127
+ }
128
+
129
+ DEVICE
130
+ static TMatrix4x4<T> identity() {
131
+ TMatrix4x4<T> m(1, 0, 0, 0,
132
+ 0, 1, 0, 0,
133
+ 0, 0, 1, 0,
134
+ 0, 0, 0, 1);
135
+ return m;
136
+ }
137
+
138
+ T data[4][4];
139
+ };
140
+
141
+ using Matrix4x4 = TMatrix4x4<Real>;
142
+ using Matrix4x4f = TMatrix4x4<float>;
143
+
144
+ template <typename T0, typename T1>
145
+ DEVICE
146
+ inline auto operator+(const TMatrix3x3<T0> &m0, const TMatrix3x3<T1> &m1) -> TMatrix3x3<decltype(m0(0, 0) + m1(0, 0))> {
147
+ TMatrix3x3<decltype(m0(0, 0) + m1(0, 0))> m;
148
+ for (int i = 0; i < 3; i++) {
149
+ for (int j = 0; j < 3; j++) {
150
+ m(i, j) = m0(i, j) + m1(i, j);
151
+ }
152
+ }
153
+ return m;
154
+ }
155
+
156
+ template <typename T0, typename T1>
157
+ DEVICE
158
+ inline auto operator-(const TMatrix3x3<T0> &m0, const TMatrix3x3<T1> &m1) -> TMatrix3x3<decltype(m0(0, 0) - m1(0, 0))> {
159
+ TMatrix3x3<decltype(m0(0, 0) - m1(0, 0))> m;
160
+ for (int i = 0; i < 3; i++) {
161
+ for (int j = 0; j < 3; j++) {
162
+ m(i, j) = m0(i, j) - m1(i, j);
163
+ }
164
+ }
165
+ return m;
166
+ }
167
+
168
+ template <typename T>
169
+ DEVICE
170
+ inline auto operator*(const TMatrix3x3<T> &m0, const TMatrix3x3<T> &m1) -> TMatrix3x3<T> {
171
+ TMatrix3x3<T> ret;
172
+ for (int i = 0; i < 3; i++) {
173
+ for (int j = 0; j < 3; j++) {
174
+ ret(i, j) = T(0);
175
+ for (int k = 0; k < 3; k++) {
176
+ ret(i, j) += m0(i, k) * m1(k, j);
177
+ }
178
+ }
179
+ }
180
+ return ret;
181
+ }
182
+
183
+ template <typename T>
184
+ DEVICE
185
+ inline auto operator*(const TVector3<T> &v, const TMatrix3x3<T> &m) -> TVector3<T> {
186
+ TVector3<T> ret;
187
+ for (int i = 0; i < 3; i++) {
188
+ ret[i] = T(0);
189
+ for (int j = 0; j < 3; j++) {
190
+ ret[i] += v[j] * m(j, i);
191
+ }
192
+ }
193
+ return ret;
194
+ }
195
+
196
+ template <typename T>
197
+ DEVICE
198
+ inline auto operator*(const TMatrix3x3<T> &m, const TVector3<T> &v) -> TVector3<T> {
199
+ TVector3<T> ret;
200
+ for (int i = 0; i < 3; i++) {
201
+ ret[i] = 0.f;
202
+ for (int j = 0; j < 3; j++) {
203
+ ret[i] += m(i, j) * v[j];
204
+ }
205
+ }
206
+ return ret;
207
+ }
208
+
209
+ template <typename T>
210
+ DEVICE
211
+ inline auto inverse(const TMatrix3x3<T> &m) -> TMatrix3x3<T> {
212
+ // computes the inverse of a matrix m
213
+ auto det = m(0, 0) * (m(1, 1) * m(2, 2) - m(2, 1) * m(1, 2)) -
214
+ m(0, 1) * (m(1, 0) * m(2, 2) - m(1, 2) * m(2, 0)) +
215
+ m(0, 2) * (m(1, 0) * m(2, 1) - m(1, 1) * m(2, 0));
216
+
217
+ auto invdet = 1 / det;
218
+
219
+ auto m_inv = TMatrix3x3<T>{};
220
+ m_inv(0, 0) = (m(1, 1) * m(2, 2) - m(2, 1) * m(1, 2)) * invdet;
221
+ m_inv(0, 1) = (m(0, 2) * m(2, 1) - m(0, 1) * m(2, 2)) * invdet;
222
+ m_inv(0, 2) = (m(0, 1) * m(1, 2) - m(0, 2) * m(1, 1)) * invdet;
223
+ m_inv(1, 0) = (m(1, 2) * m(2, 0) - m(1, 0) * m(2, 2)) * invdet;
224
+ m_inv(1, 1) = (m(0, 0) * m(2, 2) - m(0, 2) * m(2, 0)) * invdet;
225
+ m_inv(1, 2) = (m(1, 0) * m(0, 2) - m(0, 0) * m(1, 2)) * invdet;
226
+ m_inv(2, 0) = (m(1, 0) * m(2, 1) - m(2, 0) * m(1, 1)) * invdet;
227
+ m_inv(2, 1) = (m(2, 0) * m(0, 1) - m(0, 0) * m(2, 1)) * invdet;
228
+ m_inv(2, 2) = (m(0, 0) * m(1, 1) - m(1, 0) * m(0, 1)) * invdet;
229
+ return m_inv;
230
+ }
231
+
232
+ template <typename T0, typename T1>
233
+ DEVICE
234
+ inline auto operator+(const TMatrix4x4<T0> &m0, const TMatrix4x4<T1> &m1) -> TMatrix4x4<decltype(m0(0, 0) + m1(0, 0))> {
235
+ TMatrix4x4<decltype(m0(0, 0) + m1(0, 0))> m;
236
+ for (int i = 0; i < 4; i++) {
237
+ for (int j = 0; j < 4; j++) {
238
+ m(i, j) = m0(i, j) + m1(i, j);
239
+ }
240
+ }
241
+ return m;
242
+ }
243
+
244
+ template <typename T>
245
+ DEVICE
246
+ TMatrix3x3<T> transpose(const TMatrix3x3<T> &m) {
247
+ return TMatrix3x3<T>(m(0, 0), m(1, 0), m(2, 0),
248
+ m(0, 1), m(1, 1), m(2, 1),
249
+ m(0, 2), m(1, 2), m(2, 2));
250
+ }
251
+
252
+ template <typename T>
253
+ DEVICE
254
+ TMatrix4x4<T> transpose(const TMatrix4x4<T> &m) {
255
+ return TMatrix4x4<T>(m(0, 0), m(1, 0), m(2, 0), m(3, 0),
256
+ m(0, 1), m(1, 1), m(2, 1), m(3, 1),
257
+ m(0, 2), m(1, 2), m(2, 2), m(3, 2),
258
+ m(0, 3), m(1, 3), m(2, 3), m(3, 3));
259
+ }
260
+
261
+ template <typename T>
262
+ DEVICE
263
+ inline TMatrix3x3<T> operator-(const TMatrix3x3<T> &m0) {
264
+ TMatrix3x3<T> m;
265
+ for (int i = 0; i < 3; i++) {
266
+ for (int j = 0; j < 3; j++) {
267
+ m(i, j) = -m0(i, j);
268
+ }
269
+ }
270
+ return m;
271
+ }
272
+
273
+ template <typename T>
274
+ DEVICE
275
+ inline TMatrix4x4<T> operator-(const TMatrix4x4<T> &m0) {
276
+ TMatrix4x4<T> m;
277
+ for (int i = 0; i < 4; i++) {
278
+ for (int j = 0; j < 4; j++) {
279
+ m(i, j) = -m0(i, j);
280
+ }
281
+ }
282
+ return m;
283
+ }
284
+
285
+ template <typename T>
286
+ DEVICE
287
+ inline TMatrix4x4<T> operator-(const TMatrix4x4<T> &m0, const TMatrix4x4<T> &m1) {
288
+ TMatrix4x4<T> m;
289
+ for (int i = 0; i < 4; i++) {
290
+ for (int j = 0; j < 4; j++) {
291
+ m(i, j) = m0(i, j) - m1(i, j);
292
+ }
293
+ }
294
+ return m;
295
+ }
296
+
297
+ template <typename T>
298
+ DEVICE
299
+ inline TMatrix3x3<T>& operator+=(TMatrix3x3<T> &m0, const TMatrix3x3<T> &m1) {
300
+ for (int i = 0; i < 3; i++) {
301
+ for (int j = 0; j < 3; j++) {
302
+ m0(i, j) += m1(i, j);
303
+ }
304
+ }
305
+ return m0;
306
+ }
307
+
308
+ template <typename T>
309
+ DEVICE
310
+ inline TMatrix4x4<T>& operator+=(TMatrix4x4<T> &m0, const TMatrix4x4<T> &m1) {
311
+ for (int i = 0; i < 4; i++) {
312
+ for (int j = 0; j < 4; j++) {
313
+ m0(i, j) += m1(i, j);
314
+ }
315
+ }
316
+ return m0;
317
+ }
318
+
319
+ template <typename T>
320
+ DEVICE
321
+ inline TMatrix4x4<T>& operator-=(TMatrix4x4<T> &m0, const TMatrix4x4<T> &m1) {
322
+ for (int i = 0; i < 4; i++) {
323
+ for (int j = 0; j < 4; j++) {
324
+ m0(i, j) -= m1(i, j);
325
+ }
326
+ }
327
+ return m0;
328
+ }
329
+
330
+ template <typename T>
331
+ DEVICE
332
+ inline TMatrix4x4<T> operator*(const TMatrix4x4<T> &m0, const TMatrix4x4<T> &m1) {
333
+ TMatrix4x4<T> m;
334
+ for (int i = 0; i < 4; i++) {
335
+ for (int j = 0; j < 4; j++) {
336
+ for (int k = 0; k < 4; k++) {
337
+ m(i, j) += m0(i, k) * m1(k, j);
338
+ }
339
+ }
340
+ }
341
+ return m;
342
+ }
343
+
344
+ template <typename T>
345
+ DEVICE
346
+ TMatrix4x4<T> inverse(const TMatrix4x4<T> &m) {
347
+ // https://stackoverflow.com/questions/1148309/inverting-a-4x4-matrix
348
+ TMatrix4x4<T> inv;
349
+
350
+ inv(0, 0) = m(1, 1) * m(2, 2) * m(3, 3) -
351
+ m(1, 1) * m(2, 3) * m(3, 2) -
352
+ m(2, 1) * m(1, 2) * m(3, 3) +
353
+ m(2, 1) * m(1, 3) * m(3, 2) +
354
+ m(3, 1) * m(1, 2) * m(2, 3) -
355
+ m(3, 1) * m(1, 3) * m(2, 2);
356
+
357
+ inv(1, 0) = -m(1, 0) * m(2, 2) * m(3, 3) +
358
+ m(1, 0) * m(2, 3) * m(3, 2) +
359
+ m(2, 0) * m(1, 2) * m(3, 3) -
360
+ m(2, 0) * m(1, 3) * m(3, 2) -
361
+ m(3, 0) * m(1, 2) * m(2, 3) +
362
+ m(3, 0) * m(1, 3) * m(2, 2);
363
+
364
+ inv(2, 0) = m(1, 0) * m(2, 1) * m(3, 3) -
365
+ m(1, 0) * m(2, 3) * m(3, 1) -
366
+ m(2, 0) * m(1, 1) * m(3, 3) +
367
+ m(2, 0) * m(1, 3) * m(3, 1) +
368
+ m(3, 0) * m(1, 1) * m(2, 3) -
369
+ m(3, 0) * m(1, 3) * m(2, 1);
370
+
371
+ inv(3, 0) = -m(1, 0) * m(2, 1) * m(3, 2) +
372
+ m(1, 0) * m(2, 2) * m(3, 1) +
373
+ m(2, 0) * m(1, 1) * m(3, 2) -
374
+ m(2, 0) * m(1, 2) * m(3, 1) -
375
+ m(3, 0) * m(1, 1) * m(2, 2) +
376
+ m(3, 0) * m(1, 2) * m(2, 1);
377
+
378
+ inv(0, 1) = -m(0, 1) * m(2, 2) * m(3, 3) +
379
+ m(0, 1) * m(2, 3) * m(3, 2) +
380
+ m(2, 1) * m(0, 2) * m(3, 3) -
381
+ m(2, 1) * m(0, 3) * m(3, 2) -
382
+ m(3, 1) * m(0, 2) * m(2, 3) +
383
+ m(3, 1) * m(0, 3) * m(2, 2);
384
+
385
+ inv(1, 1) = m(0, 0) * m(2, 2) * m(3, 3) -
386
+ m(0, 0) * m(2, 3) * m(3, 2) -
387
+ m(2, 0) * m(0, 2) * m(3, 3) +
388
+ m(2, 0) * m(0, 3) * m(3, 2) +
389
+ m(3, 0) * m(0, 2) * m(2, 3) -
390
+ m(3, 0) * m(0, 3) * m(2, 2);
391
+
392
+ inv(2, 1) = -m(0, 0) * m(2, 1) * m(3, 3) +
393
+ m(0, 0) * m(2, 3) * m(3, 1) +
394
+ m(2, 0) * m(0, 1) * m(3, 3) -
395
+ m(2, 0) * m(0, 3) * m(3, 1) -
396
+ m(3, 0) * m(0, 1) * m(2, 3) +
397
+ m(3, 0) * m(0, 3) * m(2, 1);
398
+
399
+ inv(3, 1) = m(0, 0) * m(2, 1) * m(3, 2) -
400
+ m(0, 0) * m(2, 2) * m(3, 1) -
401
+ m(2, 0) * m(0, 1) * m(3, 2) +
402
+ m(2, 0) * m(0, 2) * m(3, 1) +
403
+ m(3, 0) * m(0, 1) * m(2, 2) -
404
+ m(3, 0) * m(0, 2) * m(2, 1);
405
+
406
+ inv(0, 2) = m(0, 1) * m(1, 2) * m(3, 3) -
407
+ m(0, 1) * m(1, 3) * m(3, 2) -
408
+ m(1, 1) * m(0, 2) * m(3, 3) +
409
+ m(1, 1) * m(0, 3) * m(3, 2) +
410
+ m(3, 1) * m(0, 2) * m(1, 3) -
411
+ m(3, 1) * m(0, 3) * m(1, 2);
412
+
413
+ inv(1, 2) = -m(0, 0) * m(1, 2) * m(3, 3) +
414
+ m(0, 0) * m(1, 3) * m(3, 2) +
415
+ m(1, 0) * m(0, 2) * m(3, 3) -
416
+ m(1, 0) * m(0, 3) * m(3, 2) -
417
+ m(3, 0) * m(0, 2) * m(1, 3) +
418
+ m(3, 0) * m(0, 3) * m(1, 2);
419
+
420
+ inv(2, 2) = m(0, 0) * m(1, 1) * m(3, 3) -
421
+ m(0, 0) * m(1, 3) * m(3, 1) -
422
+ m(1, 0) * m(0, 1) * m(3, 3) +
423
+ m(1, 0) * m(0, 3) * m(3, 1) +
424
+ m(3, 0) * m(0, 1) * m(1, 3) -
425
+ m(3, 0) * m(0, 3) * m(1, 1);
426
+
427
+ inv(3, 2) = -m(0, 0) * m(1, 1) * m(3, 2) +
428
+ m(0, 0) * m(1, 2) * m(3, 1) +
429
+ m(1, 0) * m(0, 1) * m(3, 2) -
430
+ m(1, 0) * m(0, 2) * m(3, 1) -
431
+ m(3, 0) * m(0, 1) * m(1, 2) +
432
+ m(3, 0) * m(0, 2) * m(1, 1);
433
+
434
+ inv(0, 3) = -m(0, 1) * m(1, 2) * m(2, 3) +
435
+ m(0, 1) * m(1, 3) * m(2, 2) +
436
+ m(1, 1) * m(0, 2) * m(2, 3) -
437
+ m(1, 1) * m(0, 3) * m(2, 2) -
438
+ m(2, 1) * m(0, 2) * m(1, 3) +
439
+ m(2, 1) * m(0, 3) * m(1, 2);
440
+
441
+ inv(1, 3) = m(0, 0) * m(1, 2) * m(2, 3) -
442
+ m(0, 0) * m(1, 3) * m(2, 2) -
443
+ m(1, 0) * m(0, 2) * m(2, 3) +
444
+ m(1, 0) * m(0, 3) * m(2, 2) +
445
+ m(2, 0) * m(0, 2) * m(1, 3) -
446
+ m(2, 0) * m(0, 3) * m(1, 2);
447
+
448
+ inv(2, 3) = -m(0, 0) * m(1, 1) * m(2, 3) +
449
+ m(0, 0) * m(1, 3) * m(2, 1) +
450
+ m(1, 0) * m(0, 1) * m(2, 3) -
451
+ m(1, 0) * m(0, 3) * m(2, 1) -
452
+ m(2, 0) * m(0, 1) * m(1, 3) +
453
+ m(2, 0) * m(0, 3) * m(1, 1);
454
+
455
+ inv(3, 3) = m(0, 0) * m(1, 1) * m(2, 2) -
456
+ m(0, 0) * m(1, 2) * m(2, 1) -
457
+ m(1, 0) * m(0, 1) * m(2, 2) +
458
+ m(1, 0) * m(0, 2) * m(2, 1) +
459
+ m(2, 0) * m(0, 1) * m(1, 2) -
460
+ m(2, 0) * m(0, 2) * m(1, 1);
461
+
462
+ auto det = m(0, 0) * inv(0, 0) +
463
+ m(0, 1) * inv(1, 0) +
464
+ m(0, 2) * inv(2, 0) +
465
+ m(0, 3) * inv(3, 0);
466
+
467
+ if (det == 0) {
468
+ return TMatrix4x4<T>{};
469
+ }
470
+
471
+ auto inv_det = 1.0 / det;
472
+
473
+ for (int i = 0; i < 4; i++) {
474
+ for (int j = 0; j < 4; j++) {
475
+ inv(i, j) *= inv_det;
476
+ }
477
+ }
478
+
479
+ return inv;
480
+ }
481
+
482
+ template <typename T>
483
+ inline std::ostream& operator<<(std::ostream &os, const TMatrix3x3<T> &m) {
484
+ for (int i = 0; i < 3; i++) {
485
+ for (int j = 0; j < 3; j++) {
486
+ os << m(i, j) << " ";
487
+ }
488
+ os << std::endl;
489
+ }
490
+ return os;
491
+ }
492
+
493
+ template <typename T>
494
+ inline std::ostream& operator<<(std::ostream &os, const TMatrix4x4<T> &m) {
495
+ for (int i = 0; i < 4; i++) {
496
+ for (int j = 0; j < 4; j++) {
497
+ os << m(i, j) << " ";
498
+ }
499
+ os << std::endl;
500
+ }
501
+ return os;
502
+ }
503
+
504
+ template <typename T>
505
+ DEVICE
506
+ TVector2<T> xform_pt(const TMatrix3x3<T> &m, const TVector2<T> &pt) {
507
+ TVector3<T> t{m(0, 0) * pt[0] + m(0, 1) * pt[1] + m(0, 2),
508
+ m(1, 0) * pt[0] + m(1, 1) * pt[1] + m(1, 2),
509
+ m(2, 0) * pt[0] + m(2, 1) * pt[1] + m(2, 2)};
510
+ return TVector2<T>{t[0] / t[2], t[1] / t[2]};
511
+ }
512
+
513
+ template <typename T>
514
+ DEVICE
515
+ void d_xform_pt(const TMatrix3x3<T> &m, const TVector2<T> &pt,
516
+ const TVector2<T> &d_out,
517
+ TMatrix3x3<T> &d_m,
518
+ TVector2<T> &d_pt) {
519
+ TVector3<T> t{m(0, 0) * pt[0] + m(0, 1) * pt[1] + m(0, 2),
520
+ m(1, 0) * pt[0] + m(1, 1) * pt[1] + m(1, 2),
521
+ m(2, 0) * pt[0] + m(2, 1) * pt[1] + m(2, 2)};
522
+ auto out = TVector2<T>{t[0] / t[2], t[1] / t[2]};
523
+ TVector3<T> d_t{d_out[0] / t[2],
524
+ d_out[1] / t[2],
525
+ -(d_out[0] * out[0] + d_out[1] * out[1]) / t[2]};
526
+ d_m(0, 0) += d_t[0] * pt[0];
527
+ d_m(0, 1) += d_t[0] * pt[1];
528
+ d_m(0, 2) += d_t[0];
529
+ d_m(1, 0) += d_t[1] * pt[0];
530
+ d_m(1, 1) += d_t[1] * pt[1];
531
+ d_m(1, 2) += d_t[1];
532
+ d_m(2, 0) += d_t[2] * pt[0];
533
+ d_m(2, 1) += d_t[2] * pt[1];
534
+ d_m(2, 2) += d_t[2];
535
+ d_pt[0] += d_t[0] * m(0, 0) + d_t[1] * m(1, 0) + d_t[2] * m(2, 0);
536
+ d_pt[1] += d_t[0] * m(0, 1) + d_t[1] * m(1, 1) + d_t[2] * m(2, 1);
537
+ }
538
+
539
+ template <typename T>
540
+ DEVICE
541
+ TVector2<T> xform_normal(const TMatrix3x3<T> &m_inv, const TVector2<T> &n) {
542
+ return normalize(TVector2<T>{m_inv(0, 0) * n[0] + m_inv(1, 0) * n[1],
543
+ m_inv(0, 1) * n[0] + m_inv(1, 1) * n[1]});
544
+ }
model_config/model_name_p5_all.csv ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ yolov5n
2
+ yolov5s
3
+ yolov5m
4
+ yolov5l
5
+ yolov5x
model_config/model_name_p5_all.yaml ADDED
@@ -0,0 +1 @@
 
 
1
+ model_names: ["yolov5n", "yolov5s", "yolov5m", "yolov5l", "yolov5x"]
model_config/model_name_p5_n.csv ADDED
@@ -0,0 +1 @@
 
 
1
+ yolov5n
model_config/model_name_p5_n.yaml ADDED
@@ -0,0 +1 @@
 
 
1
+ model_names: ["yolov5n"]
model_config/model_name_p6_all.csv ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ yolov5n6
2
+ yolov5s6
3
+ yolov5m6
4
+ yolov5l6
5
+ yolov5x6
model_config/model_name_p6_all.yaml ADDED
@@ -0,0 +1 @@
 
 
1
+ model_names: ["yolov5n6", "yolov5s6", "yolov5m6", "yolov5l6", "yolov5x6"]