SemanticTypography commited on
Commit
7cbaeb9
1 Parent(s): 559bee8

New Release

Browse files
LICENSE ADDED
@@ -0,0 +1,437 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Attribution-NonCommercial-ShareAlike 4.0 International
2
+
3
+ =======================================================================
4
+
5
+ Creative Commons Corporation ("Creative Commons") is not a law firm and
6
+ does not provide legal services or legal advice. Distribution of
7
+ Creative Commons public licenses does not create a lawyer-client or
8
+ other relationship. Creative Commons makes its licenses and related
9
+ information available on an "as-is" basis. Creative Commons gives no
10
+ warranties regarding its licenses, any material licensed under their
11
+ terms and conditions, or any related information. Creative Commons
12
+ disclaims all liability for damages resulting from their use to the
13
+ fullest extent possible.
14
+
15
+ Using Creative Commons Public Licenses
16
+
17
+ Creative Commons public licenses provide a standard set of terms and
18
+ conditions that creators and other rights holders may use to share
19
+ original works of authorship and other material subject to copyright
20
+ and certain other rights specified in the public license below. The
21
+ following considerations are for informational purposes only, are not
22
+ exhaustive, and do not form part of our licenses.
23
+
24
+ Considerations for licensors: Our public licenses are
25
+ intended for use by those authorized to give the public
26
+ permission to use material in ways otherwise restricted by
27
+ copyright and certain other rights. Our licenses are
28
+ irrevocable. Licensors should read and understand the terms
29
+ and conditions of the license they choose before applying it.
30
+ Licensors should also secure all rights necessary before
31
+ applying our licenses so that the public can reuse the
32
+ material as expected. Licensors should clearly mark any
33
+ material not subject to the license. This includes other CC-
34
+ licensed material, or material used under an exception or
35
+ limitation to copyright. More considerations for licensors:
36
+ wiki.creativecommons.org/Considerations_for_licensors
37
+
38
+ Considerations for the public: By using one of our public
39
+ licenses, a licensor grants the public permission to use the
40
+ licensed material under specified terms and conditions. If
41
+ the licensor's permission is not necessary for any reason--for
42
+ example, because of any applicable exception or limitation to
43
+ copyright--then that use is not regulated by the license. Our
44
+ licenses grant only permissions under copyright and certain
45
+ other rights that a licensor has authority to grant. Use of
46
+ the licensed material may still be restricted for other
47
+ reasons, including because others have copyright or other
48
+ rights in the material. A licensor may make special requests,
49
+ such as asking that all changes be marked or described.
50
+ Although not required by our licenses, you are encouraged to
51
+ respect those requests where reasonable. More considerations
52
+ for the public:
53
+ wiki.creativecommons.org/Considerations_for_licensees
54
+
55
+ =======================================================================
56
+
57
+ Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International
58
+ Public License
59
+
60
+ By exercising the Licensed Rights (defined below), You accept and agree
61
+ to be bound by the terms and conditions of this Creative Commons
62
+ Attribution-NonCommercial-ShareAlike 4.0 International Public License
63
+ ("Public License"). To the extent this Public License may be
64
+ interpreted as a contract, You are granted the Licensed Rights in
65
+ consideration of Your acceptance of these terms and conditions, and the
66
+ Licensor grants You such rights in consideration of benefits the
67
+ Licensor receives from making the Licensed Material available under
68
+ these terms and conditions.
69
+
70
+
71
+ Section 1 -- Definitions.
72
+
73
+ a. Adapted Material means material subject to Copyright and Similar
74
+ Rights that is derived from or based upon the Licensed Material
75
+ and in which the Licensed Material is translated, altered,
76
+ arranged, transformed, or otherwise modified in a manner requiring
77
+ permission under the Copyright and Similar Rights held by the
78
+ Licensor. For purposes of this Public License, where the Licensed
79
+ Material is a musical work, performance, or sound recording,
80
+ Adapted Material is always produced where the Licensed Material is
81
+ synched in timed relation with a moving image.
82
+
83
+ b. Adapter's License means the license You apply to Your Copyright
84
+ and Similar Rights in Your contributions to Adapted Material in
85
+ accordance with the terms and conditions of this Public License.
86
+
87
+ c. BY-NC-SA Compatible License means a license listed at
88
+ creativecommons.org/compatiblelicenses, approved by Creative
89
+ Commons as essentially the equivalent of this Public License.
90
+
91
+ d. Copyright and Similar Rights means copyright and/or similar rights
92
+ closely related to copyright including, without limitation,
93
+ performance, broadcast, sound recording, and Sui Generis Database
94
+ Rights, without regard to how the rights are labeled or
95
+ categorized. For purposes of this Public License, the rights
96
+ specified in Section 2(b)(1)-(2) are not Copyright and Similar
97
+ Rights.
98
+
99
+ e. Effective Technological Measures means those measures that, in the
100
+ absence of proper authority, may not be circumvented under laws
101
+ fulfilling obligations under Article 11 of the WIPO Copyright
102
+ Treaty adopted on December 20, 1996, and/or similar international
103
+ agreements.
104
+
105
+ f. Exceptions and Limitations means fair use, fair dealing, and/or
106
+ any other exception or limitation to Copyright and Similar Rights
107
+ that applies to Your use of the Licensed Material.
108
+
109
+ g. License Elements means the license attributes listed in the name
110
+ of a Creative Commons Public License. The License Elements of this
111
+ Public License are Attribution, NonCommercial, and ShareAlike.
112
+
113
+ h. Licensed Material means the artistic or literary work, database,
114
+ or other material to which the Licensor applied this Public
115
+ License.
116
+
117
+ i. Licensed Rights means the rights granted to You subject to the
118
+ terms and conditions of this Public License, which are limited to
119
+ all Copyright and Similar Rights that apply to Your use of the
120
+ Licensed Material and that the Licensor has authority to license.
121
+
122
+ j. Licensor means the individual(s) or entity(ies) granting rights
123
+ under this Public License.
124
+
125
+ k. NonCommercial means not primarily intended for or directed towards
126
+ commercial advantage or monetary compensation. For purposes of
127
+ this Public License, the exchange of the Licensed Material for
128
+ other material subject to Copyright and Similar Rights by digital
129
+ file-sharing or similar means is NonCommercial provided there is
130
+ no payment of monetary compensation in connection with the
131
+ exchange.
132
+
133
+ l. Share means to provide material to the public by any means or
134
+ process that requires permission under the Licensed Rights, such
135
+ as reproduction, public display, public performance, distribution,
136
+ dissemination, communication, or importation, and to make material
137
+ available to the public including in ways that members of the
138
+ public may access the material from a place and at a time
139
+ individually chosen by them.
140
+
141
+ m. Sui Generis Database Rights means rights other than copyright
142
+ resulting from Directive 96/9/EC of the European Parliament and of
143
+ the Council of 11 March 1996 on the legal protection of databases,
144
+ as amended and/or succeeded, as well as other essentially
145
+ equivalent rights anywhere in the world.
146
+
147
+ n. You means the individual or entity exercising the Licensed Rights
148
+ under this Public License. Your has a corresponding meaning.
149
+
150
+
151
+ Section 2 -- Scope.
152
+
153
+ a. License grant.
154
+
155
+ 1. Subject to the terms and conditions of this Public License,
156
+ the Licensor hereby grants You a worldwide, royalty-free,
157
+ non-sublicensable, non-exclusive, irrevocable license to
158
+ exercise the Licensed Rights in the Licensed Material to:
159
+
160
+ a. reproduce and Share the Licensed Material, in whole or
161
+ in part, for NonCommercial purposes only; and
162
+
163
+ b. produce, reproduce, and Share Adapted Material for
164
+ NonCommercial purposes only.
165
+
166
+ 2. Exceptions and Limitations. For the avoidance of doubt, where
167
+ Exceptions and Limitations apply to Your use, this Public
168
+ License does not apply, and You do not need to comply with
169
+ its terms and conditions.
170
+
171
+ 3. Term. The term of this Public License is specified in Section
172
+ 6(a).
173
+
174
+ 4. Media and formats; technical modifications allowed. The
175
+ Licensor authorizes You to exercise the Licensed Rights in
176
+ all media and formats whether now known or hereafter created,
177
+ and to make technical modifications necessary to do so. The
178
+ Licensor waives and/or agrees not to assert any right or
179
+ authority to forbid You from making technical modifications
180
+ necessary to exercise the Licensed Rights, including
181
+ technical modifications necessary to circumvent Effective
182
+ Technological Measures. For purposes of this Public License,
183
+ simply making modifications authorized by this Section 2(a)
184
+ (4) never produces Adapted Material.
185
+
186
+ 5. Downstream recipients.
187
+
188
+ a. Offer from the Licensor -- Licensed Material. Every
189
+ recipient of the Licensed Material automatically
190
+ receives an offer from the Licensor to exercise the
191
+ Licensed Rights under the terms and conditions of this
192
+ Public License.
193
+
194
+ b. Additional offer from the Licensor -- Adapted Material.
195
+ Every recipient of Adapted Material from You
196
+ automatically receives an offer from the Licensor to
197
+ exercise the Licensed Rights in the Adapted Material
198
+ under the conditions of the Adapter's License You apply.
199
+
200
+ c. No downstream restrictions. You may not offer or impose
201
+ any additional or different terms or conditions on, or
202
+ apply any Effective Technological Measures to, the
203
+ Licensed Material if doing so restricts exercise of the
204
+ Licensed Rights by any recipient of the Licensed
205
+ Material.
206
+
207
+ 6. No endorsement. Nothing in this Public License constitutes or
208
+ may be construed as permission to assert or imply that You
209
+ are, or that Your use of the Licensed Material is, connected
210
+ with, or sponsored, endorsed, or granted official status by,
211
+ the Licensor or others designated to receive attribution as
212
+ provided in Section 3(a)(1)(A)(i).
213
+
214
+ b. Other rights.
215
+
216
+ 1. Moral rights, such as the right of integrity, are not
217
+ licensed under this Public License, nor are publicity,
218
+ privacy, and/or other similar personality rights; however, to
219
+ the extent possible, the Licensor waives and/or agrees not to
220
+ assert any such rights held by the Licensor to the limited
221
+ extent necessary to allow You to exercise the Licensed
222
+ Rights, but not otherwise.
223
+
224
+ 2. Patent and trademark rights are not licensed under this
225
+ Public License.
226
+
227
+ 3. To the extent possible, the Licensor waives any right to
228
+ collect royalties from You for the exercise of the Licensed
229
+ Rights, whether directly or through a collecting society
230
+ under any voluntary or waivable statutory or compulsory
231
+ licensing scheme. In all other cases the Licensor expressly
232
+ reserves any right to collect such royalties, including when
233
+ the Licensed Material is used other than for NonCommercial
234
+ purposes.
235
+
236
+
237
+ Section 3 -- License Conditions.
238
+
239
+ Your exercise of the Licensed Rights is expressly made subject to the
240
+ following conditions.
241
+
242
+ a. Attribution.
243
+
244
+ 1. If You Share the Licensed Material (including in modified
245
+ form), You must:
246
+
247
+ a. retain the following if it is supplied by the Licensor
248
+ with the Licensed Material:
249
+
250
+ i. identification of the creator(s) of the Licensed
251
+ Material and any others designated to receive
252
+ attribution, in any reasonable manner requested by
253
+ the Licensor (including by pseudonym if
254
+ designated);
255
+
256
+ ii. a copyright notice;
257
+
258
+ iii. a notice that refers to this Public License;
259
+
260
+ iv. a notice that refers to the disclaimer of
261
+ warranties;
262
+
263
+ v. a URI or hyperlink to the Licensed Material to the
264
+ extent reasonably practicable;
265
+
266
+ b. indicate if You modified the Licensed Material and
267
+ retain an indication of any previous modifications; and
268
+
269
+ c. indicate the Licensed Material is licensed under this
270
+ Public License, and include the text of, or the URI or
271
+ hyperlink to, this Public License.
272
+
273
+ 2. You may satisfy the conditions in Section 3(a)(1) in any
274
+ reasonable manner based on the medium, means, and context in
275
+ which You Share the Licensed Material. For example, it may be
276
+ reasonable to satisfy the conditions by providing a URI or
277
+ hyperlink to a resource that includes the required
278
+ information.
279
+ 3. If requested by the Licensor, You must remove any of the
280
+ information required by Section 3(a)(1)(A) to the extent
281
+ reasonably practicable.
282
+
283
+ b. ShareAlike.
284
+
285
+ In addition to the conditions in Section 3(a), if You Share
286
+ Adapted Material You produce, the following conditions also apply.
287
+
288
+ 1. The Adapter's License You apply must be a Creative Commons
289
+ license with the same License Elements, this version or
290
+ later, or a BY-NC-SA Compatible License.
291
+
292
+ 2. You must include the text of, or the URI or hyperlink to, the
293
+ Adapter's License You apply. You may satisfy this condition
294
+ in any reasonable manner based on the medium, means, and
295
+ context in which You Share Adapted Material.
296
+
297
+ 3. You may not offer or impose any additional or different terms
298
+ or conditions on, or apply any Effective Technological
299
+ Measures to, Adapted Material that restrict exercise of the
300
+ rights granted under the Adapter's License You apply.
301
+
302
+
303
+ Section 4 -- Sui Generis Database Rights.
304
+
305
+ Where the Licensed Rights include Sui Generis Database Rights that
306
+ apply to Your use of the Licensed Material:
307
+
308
+ a. for the avoidance of doubt, Section 2(a)(1) grants You the right
309
+ to extract, reuse, reproduce, and Share all or a substantial
310
+ portion of the contents of the database for NonCommercial purposes
311
+ only;
312
+
313
+ b. if You include all or a substantial portion of the database
314
+ contents in a database in which You have Sui Generis Database
315
+ Rights, then the database in which You have Sui Generis Database
316
+ Rights (but not its individual contents) is Adapted Material,
317
+ including for purposes of Section 3(b); and
318
+
319
+ c. You must comply with the conditions in Section 3(a) if You Share
320
+ all or a substantial portion of the contents of the database.
321
+
322
+ For the avoidance of doubt, this Section 4 supplements and does not
323
+ replace Your obligations under this Public License where the Licensed
324
+ Rights include other Copyright and Similar Rights.
325
+
326
+
327
+ Section 5 -- Disclaimer of Warranties and Limitation of Liability.
328
+
329
+ a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
330
+ EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
331
+ AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
332
+ ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
333
+ IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
334
+ WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
335
+ PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
336
+ ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
337
+ KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
338
+ ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
339
+
340
+ b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
341
+ TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
342
+ NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
343
+ INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
344
+ COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
345
+ USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
346
+ ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
347
+ DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
348
+ IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
349
+
350
+ c. The disclaimer of warranties and limitation of liability provided
351
+ above shall be interpreted in a manner that, to the extent
352
+ possible, most closely approximates an absolute disclaimer and
353
+ waiver of all liability.
354
+
355
+
356
+ Section 6 -- Term and Termination.
357
+
358
+ a. This Public License applies for the term of the Copyright and
359
+ Similar Rights licensed here. However, if You fail to comply with
360
+ this Public License, then Your rights under this Public License
361
+ terminate automatically.
362
+
363
+ b. Where Your right to use the Licensed Material has terminated under
364
+ Section 6(a), it reinstates:
365
+
366
+ 1. automatically as of the date the violation is cured, provided
367
+ it is cured within 30 days of Your discovery of the
368
+ violation; or
369
+
370
+ 2. upon express reinstatement by the Licensor.
371
+
372
+ For the avoidance of doubt, this Section 6(b) does not affect any
373
+ right the Licensor may have to seek remedies for Your violations
374
+ of this Public License.
375
+
376
+ c. For the avoidance of doubt, the Licensor may also offer the
377
+ Licensed Material under separate terms or conditions or stop
378
+ distributing the Licensed Material at any time; however, doing so
379
+ will not terminate this Public License.
380
+
381
+ d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
382
+ License.
383
+
384
+
385
+ Section 7 -- Other Terms and Conditions.
386
+
387
+ a. The Licensor shall not be bound by any additional or different
388
+ terms or conditions communicated by You unless expressly agreed.
389
+
390
+ b. Any arrangements, understandings, or agreements regarding the
391
+ Licensed Material not stated herein are separate from and
392
+ independent of the terms and conditions of this Public License.
393
+
394
+
395
+ Section 8 -- Interpretation.
396
+
397
+ a. For the avoidance of doubt, this Public License does not, and
398
+ shall not be interpreted to, reduce, limit, restrict, or impose
399
+ conditions on any use of the Licensed Material that could lawfully
400
+ be made without permission under this Public License.
401
+
402
+ b. To the extent possible, if any provision of this Public License is
403
+ deemed unenforceable, it shall be automatically reformed to the
404
+ minimum extent necessary to make it enforceable. If the provision
405
+ cannot be reformed, it shall be severed from this Public License
406
+ without affecting the enforceability of the remaining terms and
407
+ conditions.
408
+
409
+ c. No term or condition of this Public License will be waived and no
410
+ failure to comply consented to unless expressly agreed to by the
411
+ Licensor.
412
+
413
+ d. Nothing in this Public License constitutes or may be interpreted
414
+ as a limitation upon, or waiver of, any privileges and immunities
415
+ that apply to the Licensor or You, including from the legal
416
+ processes of any jurisdiction or authority.
417
+
418
+ =======================================================================
419
+
420
+ Creative Commons is not a party to its public
421
+ licenses. Notwithstanding, Creative Commons may elect to apply one of
422
+ its public licenses to material it publishes and in those instances
423
+ will be considered the “Licensor.” The text of the Creative Commons
424
+ public licenses is dedicated to the public domain under the CC0 Public
425
+ Domain Dedication. Except for the limited purpose of indicating that
426
+ material is shared under a Creative Commons public license or as
427
+ otherwise permitted by the Creative Commons policies published at
428
+ creativecommons.org/policies, Creative Commons does not authorize the
429
+ use of the trademark "Creative Commons" or any other trademark or logo
430
+ of Creative Commons without its prior written consent including,
431
+ without limitation, in connection with any unauthorized modifications
432
+ to any of its public licenses or any other arrangements,
433
+ understandings, or agreements concerning use of licensed material. For
434
+ the avoidance of doubt, this paragraph does not form part of the
435
+ public licenses.
436
+
437
+ Creative Commons may be contacted at creativecommons.org.
README.md CHANGED
@@ -1,12 +1,14 @@
1
  ---
2
  title: Word As Image
3
- emoji: 🏢
4
- colorFrom: yellow
5
- colorTo: indigo
6
  sdk: gradio
7
- sdk_version: 3.19.1
 
8
  app_file: app.py
9
  pinned: false
 
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
  title: Word As Image
3
+ emoji: 🚀
4
+ colorFrom: purple
5
+ colorTo: pink
6
  sdk: gradio
7
+ sdk_version: 3.21.0
8
+ python_version: 3.8.15
9
  app_file: app.py
10
  pinned: false
11
+ license: cc-by-sa-4.0
12
  ---
13
 
14
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,357 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import os
3
+ import argparse
4
+ from easydict import EasyDict as edict
5
+ import yaml
6
+ import os.path as osp
7
+ import random
8
+ import numpy.random as npr
9
+ import sys
10
+
11
+ sys.path.append('/home/user/app/code')
12
+
13
+ # set up diffvg
14
+ os.system('git clone https://github.com/BachiLi/diffvg.git')
15
+ os.chdir('diffvg')
16
+ os.system('git submodule update --init --recursive')
17
+ os.system('python setup.py install --user')
18
+ sys.path.append("/home/user/.local/lib/python3.8/site-packages/diffvg-0.0.1-py3.8-linux-x86_64.egg")
19
+
20
+ os.chdir('/home/user/app')
21
+
22
+ import torch
23
+ from diffusers import StableDiffusionPipeline
24
+
25
+
26
+ device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
27
+
28
+ model = StableDiffusionPipeline.from_pretrained("runwayml/stable-diffusion-v1-5",
29
+ torch_dtype=torch.float16, use_auth_token=os.environ['HF_TOKEN']).to(device)
30
+
31
+ from typing import Mapping
32
+ from tqdm import tqdm
33
+ import torch
34
+ from torch.optim.lr_scheduler import LambdaLR
35
+ import pydiffvg
36
+ import save_svg
37
+ from losses import SDSLoss, ToneLoss, ConformalLoss
38
+ from utils import (
39
+ edict_2_dict,
40
+ update,
41
+ check_and_create_dir,
42
+ get_data_augs,
43
+ save_image,
44
+ preprocess,
45
+ learning_rate_decay,
46
+ combine_word)
47
+ import warnings
48
+
49
+ TITLE="""<h1 style="font-size: 42px;" align="center">Word-As-Image for Semantic Typography</h1>"""
50
+ DESCRIPTION="""This is a demo for [Word-As-Image for Semantic Typography](https://wordasimage.github.io/Word-As-Image-Page/). By using Word-as-Image, a visual representation of the meaning of the word is created while maintaining legibility of the text and font style.
51
+ Please select a semantic concept word and a letter you wish to generate, it will take ~5 minutes to perform 500 iterations."""
52
+
53
+ warnings.filterwarnings("ignore")
54
+
55
+ pydiffvg.set_print_timing(False)
56
+ gamma = 1.0
57
+
58
+
59
+ def set_config(semantic_concept, word, letter, font_name, num_steps):
60
+
61
+ cfg_d = edict()
62
+ cfg_d.config = "code/config/base.yaml"
63
+ cfg_d.experiment = "demo"
64
+
65
+ with open(cfg_d.config, 'r') as f:
66
+ cfg_full = yaml.load(f, Loader=yaml.FullLoader)
67
+
68
+ cfg_key = cfg_d.experiment
69
+ cfgs = [cfg_d]
70
+ while cfg_key:
71
+ cfgs.append(cfg_full[cfg_key])
72
+ cfg_key = cfgs[-1].get('parent_config', 'baseline')
73
+
74
+ cfg = edict()
75
+ for options in reversed(cfgs):
76
+ update(cfg, options)
77
+ del cfgs
78
+
79
+ cfg.semantic_concept = semantic_concept
80
+ cfg.word = word
81
+ cfg.optimized_letter = letter
82
+ cfg.font = font_name
83
+ cfg.seed = 0
84
+ cfg.num_iter = num_steps
85
+
86
+ if ' ' in cfg.word:
87
+ raise gr.Error(f'should be only one word')
88
+ cfg.caption = f"a {cfg.semantic_concept}. {cfg.prompt_suffix}"
89
+ cfg.log_dir = f"output/{cfg.experiment}_{cfg.word}"
90
+ if cfg.optimized_letter in cfg.word:
91
+ cfg.optimized_letter = cfg.optimized_letter
92
+ else:
93
+ raise gr.Error(f'letter should be in word')
94
+
95
+ cfg.letter = f"{cfg.font}_{cfg.optimized_letter}_scaled"
96
+ cfg.target = f"code/data/init/{cfg.letter}"
97
+
98
+ # set experiment dir
99
+ signature = f"{cfg.letter}_concept_{cfg.semantic_concept}_seed_{cfg.seed}"
100
+ cfg.experiment_dir = \
101
+ osp.join(cfg.log_dir, cfg.font, signature)
102
+ configfile = osp.join(cfg.experiment_dir, 'config.yaml')
103
+
104
+ # create experiment dir and save config
105
+ check_and_create_dir(configfile)
106
+ with open(osp.join(configfile), 'w') as f:
107
+ yaml.dump(edict_2_dict(cfg), f)
108
+
109
+ if cfg.seed is not None:
110
+ random.seed(cfg.seed)
111
+ npr.seed(cfg.seed)
112
+ torch.manual_seed(cfg.seed)
113
+ torch.backends.cudnn.benchmark = False
114
+ else:
115
+ assert False
116
+ return cfg
117
+
118
+
119
+ def init_shapes(svg_path, trainable: Mapping[str, bool]):
120
+ svg = f'{svg_path}.svg'
121
+ canvas_width, canvas_height, shapes_init, shape_groups_init = pydiffvg.svg_to_scene(svg)
122
+
123
+ parameters = edict()
124
+
125
+ # path points
126
+ if trainable.point:
127
+ parameters.point = []
128
+ for path in shapes_init:
129
+ path.points.requires_grad = True
130
+ parameters.point.append(path.points)
131
+
132
+ return shapes_init, shape_groups_init, parameters
133
+
134
+
135
+ def run_main_ex(semantic_concept, word, letter, font_name, num_steps):
136
+ return list(next(run_main_app(semantic_concept, word, letter, font_name, num_steps, 1)))
137
+
138
+ def run_main_app(semantic_concept, word, letter, font_name, num_steps, example=0):
139
+
140
+ cfg = set_config(semantic_concept, word, letter, font_name, num_steps)
141
+
142
+ pydiffvg.set_use_gpu(torch.cuda.is_available())
143
+
144
+ print("preprocessing")
145
+ preprocess(cfg.font, cfg.word, cfg.optimized_letter, cfg.level_of_cc)
146
+ filename_init = os.path.join("code/data/init/", f"{cfg.font}_{cfg.word}_scaled.svg").replace(" ", "_")
147
+ if not example:
148
+ yield gr.update(value=filename_init,visible=True),gr.update(visible=False),gr.update(visible=False)
149
+
150
+ sds_loss = SDSLoss(cfg, device, model)
151
+
152
+ h, w = cfg.render_size, cfg.render_size
153
+
154
+ data_augs = get_data_augs(cfg.cut_size)
155
+
156
+ render = pydiffvg.RenderFunction.apply
157
+
158
+ # initialize shape
159
+ print('initializing shape')
160
+ shapes, shape_groups, parameters = init_shapes(svg_path=cfg.target, trainable=cfg.trainable)
161
+
162
+ scene_args = pydiffvg.RenderFunction.serialize_scene(w, h, shapes, shape_groups)
163
+ img_init = render(w, h, 2, 2, 0, None, *scene_args)
164
+ img_init = img_init[:, :, 3:4] * img_init[:, :, :3] + \
165
+ torch.ones(img_init.shape[0], img_init.shape[1], 3, device=device) * (1 - img_init[:, :, 3:4])
166
+ img_init = img_init[:, :, :3]
167
+
168
+ tone_loss = ToneLoss(cfg)
169
+ tone_loss.set_image_init(img_init)
170
+
171
+ num_iter = cfg.num_iter
172
+ pg = [{'params': parameters["point"], 'lr': cfg.lr_base["point"]}]
173
+ optim = torch.optim.Adam(pg, betas=(0.9, 0.9), eps=1e-6)
174
+
175
+ conformal_loss = ConformalLoss(parameters, device, cfg.optimized_letter, shape_groups)
176
+
177
+ lr_lambda = lambda step: learning_rate_decay(step, cfg.lr.lr_init, cfg.lr.lr_final, num_iter,
178
+ lr_delay_steps=cfg.lr.lr_delay_steps,
179
+ lr_delay_mult=cfg.lr.lr_delay_mult) / cfg.lr.lr_init
180
+
181
+ scheduler = LambdaLR(optim, lr_lambda=lr_lambda, last_epoch=-1) # lr.base * lrlambda_f
182
+
183
+ print("start training")
184
+ # training loop
185
+ t_range = tqdm(range(num_iter))
186
+ for step in t_range:
187
+ optim.zero_grad()
188
+
189
+ # render image
190
+ scene_args = pydiffvg.RenderFunction.serialize_scene(w, h, shapes, shape_groups)
191
+ img = render(w, h, 2, 2, step, None, *scene_args)
192
+
193
+ # compose image with white background
194
+ img = img[:, :, 3:4] * img[:, :, :3] + torch.ones(img.shape[0], img.shape[1], 3, device=device) * (
195
+ 1 - img[:, :, 3:4])
196
+ img = img[:, :, :3]
197
+
198
+ filename = os.path.join(
199
+ cfg.experiment_dir, "video-svg", f"iter{step:04d}.svg")
200
+ check_and_create_dir(filename)
201
+ save_svg.save_svg(filename, w, h, shapes, shape_groups)
202
+ if not example:
203
+ yield gr.update(visible=True),gr.update(value=filename, label=f'iters: {step} / {num_iter}', visible=True),gr.update(visible=False)
204
+
205
+ x = img.unsqueeze(0).permute(0, 3, 1, 2) # HWC -> NCHW
206
+ x = x.repeat(cfg.batch_size, 1, 1, 1)
207
+ x_aug = data_augs.forward(x)
208
+
209
+ # compute diffusion loss per pixel
210
+ loss = sds_loss(x_aug)
211
+
212
+ tone_loss_res = tone_loss(x, step)
213
+ loss = loss + tone_loss_res
214
+
215
+ loss_angles = conformal_loss()
216
+ loss_angles = cfg.loss.conformal.angeles_w * loss_angles
217
+ loss = loss + loss_angles
218
+
219
+ loss.backward()
220
+ optim.step()
221
+ scheduler.step()
222
+
223
+
224
+ filename = os.path.join(
225
+ cfg.experiment_dir, "output-svg", "output.svg")
226
+ check_and_create_dir(filename)
227
+ save_svg.save_svg(
228
+ filename, w, h, shapes, shape_groups)
229
+
230
+ combine_word(cfg.word, cfg.optimized_letter, cfg.font, cfg.experiment_dir)
231
+
232
+ image = os.path.join(cfg.experiment_dir,f"{cfg.font}_{cfg.word}_{cfg.optimized_letter}.svg")
233
+ yield gr.update(value=filename_init,visible=True),gr.update(visible=False),gr.update(value=image,visible=True)
234
+
235
+
236
+ with gr.Blocks() as demo:
237
+
238
+ gr.HTML(TITLE)
239
+ gr.Markdown(DESCRIPTION)
240
+
241
+ with gr.Row():
242
+ with gr.Column():
243
+
244
+ semantic_concept = gr.Text(
245
+ label='Semantic Concept',
246
+ max_lines=1,
247
+ placeholder=
248
+ 'Enter a semantic concept. For example: BUNNY'
249
+ )
250
+
251
+ word = gr.Text(
252
+ label='Word',
253
+ max_lines=1,
254
+ placeholder=
255
+ 'Enter a word. For example: BUNNY'
256
+ )
257
+
258
+ letter = gr.Text(
259
+ label='Letter',
260
+ max_lines=1,
261
+ placeholder=
262
+ 'Choose a letter in the word to optimize. For example: Y'
263
+ )
264
+
265
+ num_steps = gr.Slider(label='Optimization Iterations',
266
+ minimum=0,
267
+ maximum=500,
268
+ step=10,
269
+ value=500)
270
+
271
+ font_name = gr.Text(value=None,visible=False,label="Font Name")
272
+ gallery = gr.Gallery(value=[(os.path.join("images","KaushanScript-Regular.png"),"KaushanScript-Regular"), (os.path.join("images","IndieFlower-Regular.png"),"IndieFlower-Regular"),(os.path.join("images","Quicksand.png"),"Quicksand"),
273
+ (os.path.join("images","Saira-Regular.png"),"Saira-Regular"), (os.path.join("images","LuckiestGuy-Regular.png"),"LuckiestGuy-Regular"),(os.path.join("images","DeliusUnicase-Regular.png"),"DeliusUnicase-Regular"),
274
+ (os.path.join("images","Noteworthy-Bold.png"),"Noteworthy-Bold"), (os.path.join("images","HobeauxRococeaux-Sherman.png"),"HobeauxRococeaux-Sherman")],label="Font Name").style(grid=4)
275
+
276
+ def on_select(evt: gr.SelectData):
277
+ return evt.value
278
+
279
+ gallery.select(fn=on_select, inputs=None, outputs=font_name)
280
+
281
+ run = gr.Button('Generate')
282
+
283
+ with gr.Column():
284
+ result0 = gr.Image(type="filepath", label="Initial Word").style(height=333)
285
+ result1 = gr.Image(type="filepath", label="Optimization Process").style(height=110)
286
+ result2 = gr.Image(type="filepath", label="Final Result",visible=False).style(height=333)
287
+
288
+
289
+ with gr.Row():
290
+ # examples
291
+ examples = [
292
+ [
293
+ "BUNNY",
294
+ "BUNNY",
295
+ "Y",
296
+ "KaushanScript-Regular",
297
+ 500
298
+ ],
299
+ [
300
+ "LION",
301
+ "LION",
302
+ "O",
303
+ "Quicksand",
304
+ 500
305
+ ],
306
+ [
307
+ "FROG",
308
+ "FROG",
309
+ "G",
310
+ "IndieFlower-Regular",
311
+ 500
312
+ ],
313
+ [
314
+ "CAT",
315
+ "CAT",
316
+ "C",
317
+ "LuckiestGuy-Regular",
318
+ 500
319
+ ],
320
+ ]
321
+ demo.queue(max_size=10, concurrency_count=2)
322
+ gr.Examples(examples=examples,
323
+ inputs=[
324
+ semantic_concept,
325
+ word,
326
+ letter,
327
+ font_name,
328
+ num_steps
329
+ ],
330
+ outputs=[
331
+ result0,
332
+ result1,
333
+ result2
334
+ ],
335
+ fn=run_main_ex,
336
+ cache_examples=True)
337
+
338
+
339
+ # inputs
340
+ inputs = [
341
+ semantic_concept,
342
+ word,
343
+ letter,
344
+ font_name,
345
+ num_steps
346
+ ]
347
+
348
+ outputs = [
349
+ result0,
350
+ result1,
351
+ result2
352
+ ]
353
+
354
+ run.click(fn=run_main_app, inputs=inputs, outputs=outputs, queue=True)
355
+
356
+
357
+ demo.launch(share=False)
code/bezier.py ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import matplotlib.pyplot as plt
3
+ from scipy.special import binom
4
+ from numpy.linalg import norm
5
+
6
+ def num_bezier(n_ctrl, degree=3):
7
+ if type(n_ctrl) == np.ndarray:
8
+ n_ctrl = len(n_ctrl)
9
+ return int((n_ctrl - 1) / degree)
10
+
11
+ def bernstein(n, i):
12
+ bi = binom(n, i)
13
+ return lambda t, bi=bi, n=n, i=i: bi * t**i * (1 - t)**(n - i)
14
+
15
+ def bezier(P, t, d=0):
16
+ '''Bezier curve of degree len(P)-1. d is the derivative order (0 gives positions)'''
17
+ n = P.shape[0] - 1
18
+ if d > 0:
19
+ Q = np.diff(P, axis=0)*n
20
+ return bezier(Q, t, d-1)
21
+ B = np.vstack([bernstein(n, i)(t) for i, p in enumerate(P)])
22
+ return (P.T @ B).T
23
+
24
+ def cubic_bezier(P, t):
25
+ return (1.0-t)**3*P[0] + 3*(1.0-t)**2*t*P[1] + 3*(1.0-t)*t**2*P[2] + t**3*P[3]
26
+
27
+ def bezier_piecewise(Cp, subd=100, degree=3, d=0):
28
+ ''' sample a piecewise Bezier curve given a sequence of control points'''
29
+ num = num_bezier(Cp.shape[0], degree)
30
+ X = []
31
+ for i in range(num):
32
+ P = Cp[i*degree:i*degree+degree+1, :]
33
+ t = np.linspace(0, 1., subd)[:-1]
34
+ Y = bezier(P, t, d)
35
+ X += [Y]
36
+ X.append(Cp[-1])
37
+ X = np.vstack(X)
38
+ return X
39
+
40
+ def compute_beziers(beziers, subd=100, degree=3):
41
+ chain = beziers_to_chain(beziers)
42
+ return bezier_piecewise(chain, subd, degree)
43
+
44
+ def plot_control_polygon(Cp, degree=3, lw=0.5, linecolor=np.ones(3)*0.1):
45
+ n_bezier = num_bezier(len(Cp), degree)
46
+ for i in range(n_bezier):
47
+ cp = Cp[i*degree:i*degree+degree+1, :]
48
+ if degree==3:
49
+ plt.plot(cp[0:2,0], cp[0:2, 1], ':', color=linecolor, linewidth=lw)
50
+ plt.plot(cp[2:,0], cp[2:,1], ':', color=linecolor, linewidth=lw)
51
+ plt.plot(cp[:,0], cp[:,1], 'o', color=[0, 0.5, 1.], markersize=4)
52
+ else:
53
+ plt.plot(cp[:,0], cp[:,1], ':', color=linecolor, linewidth=lw)
54
+ plt.plot(cp[:,0], cp[:,1], 'o', color=[0, 0.5, 1.])
55
+
56
+
57
+ def chain_to_beziers(chain, degree=3):
58
+ ''' Convert Bezier chain to list of curve segments (4 control points each)'''
59
+ num = num_bezier(chain.shape[0], degree)
60
+ beziers = []
61
+ for i in range(num):
62
+ beziers.append(chain[i*degree:i*degree+degree+1,:])
63
+ return beziers
64
+
65
+
66
+ def beziers_to_chain(beziers):
67
+ ''' Convert list of Bezier curve segments to a piecewise bezier chain (shares vertices)'''
68
+ n = len(beziers)
69
+ chain = []
70
+ for i in range(n):
71
+ chain.append(list(beziers[i][:-1]))
72
+ chain.append([beziers[-1][-1]])
73
+ return np.array(sum(chain, []))
74
+
75
+
76
+ def split_cubic(bez, t):
77
+ p1, p2, p3, p4 = bez
78
+
79
+ p12 = (p2 - p1) * t + p1
80
+ p23 = (p3 - p2) * t + p2
81
+ p34 = (p4 - p3) * t + p3
82
+
83
+ p123 = (p23 - p12) * t + p12
84
+ p234 = (p34 - p23) * t + p23
85
+
86
+ p1234 = (p234 - p123) * t + p123
87
+
88
+ return np.array([p1, p12, p123, p1234]), np.array([p1234, p234, p34, p4])
89
+
90
+
91
+ def approx_arc_length(bez):
92
+ c0, c1, c2, c3 = bez
93
+ v0 = norm(c1-c0)*0.15
94
+ v1 = norm(-0.558983582205757*c0 + 0.325650248872424*c1 + 0.208983582205757*c2 + 0.024349751127576*c3)
95
+ v2 = norm(c3-c0+c2-c1)*0.26666666666666666
96
+ v3 = norm(-0.024349751127576*c0 - 0.208983582205757*c1 - 0.325650248872424*c2 + 0.558983582205757*c3)
97
+ v4 = norm(c3-c2)*.15
98
+ return v0 + v1 + v2 + v3 + v4
99
+
100
+
101
+ def subdivide_bezier(bez, thresh):
102
+ stack = [bez]
103
+ res = []
104
+ while stack:
105
+ bez = stack.pop()
106
+ l = approx_arc_length(bez)
107
+ if l < thresh:
108
+ res.append(bez)
109
+ else:
110
+ b1, b2 = split_cubic(bez, 0.5)
111
+ stack += [b2, b1]
112
+ return res
113
+
114
+ def subdivide_bezier_chain(C, thresh):
115
+ beziers = chain_to_beziers(C)
116
+ res = []
117
+ for bez in beziers:
118
+ res += subdivide_bezier(bez, thresh)
119
+ return beziers_to_chain(res)
120
+
121
+
122
+
code/config/base.yaml ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ baseline:
2
+ parent_config: ''
3
+ save:
4
+ init: true
5
+ image: true
6
+ video: true
7
+ video_frame_freq: 1
8
+ trainable:
9
+ point: true
10
+ lr_base:
11
+ point: 1
12
+ lr:
13
+ lr_init: 0.002
14
+ lr_final: 0.0008
15
+ lr_delay_mult: 0.1
16
+ lr_delay_steps: 100
17
+ num_iter: 500
18
+ render_size: 600
19
+ cut_size: 512
20
+ level_of_cc: 0 # 0 - original number of cc / 1 - recommended / 2 - more control points
21
+ seed: 0
22
+ diffusion:
23
+ model: "runwayml/stable-diffusion-v1-5"
24
+ timesteps: 1000
25
+ guidance_scale: 100
26
+ loss:
27
+ use_sds_loss: true
28
+ tone:
29
+ use_tone_loss: false
30
+ conformal:
31
+ use_conformal_loss: false
32
+
33
+ conformal_0.5_dist_pixel_100_kernel201:
34
+ parent_config: baseline
35
+ level_of_cc: 1
36
+ prompt_suffix: "minimal flat 2d vector. lineal color. minimal flat 2d vector. lineal color."
37
+ batch_size: 1
38
+ loss:
39
+ tone:
40
+ use_tone_loss: true
41
+ dist_loss_weight: 100
42
+ pixel_dist_kernel_blur: 201
43
+ pixel_dist_sigma: 30
44
+ conformal:
45
+ use_conformal_loss: true
46
+ angeles_w: 0.5
47
+
48
+ demo:
49
+ parent_config: conformal_0.5_dist_pixel_100_kernel201
50
+ token: false
code/data/fonts/Bell MT.ttf ADDED
Binary file (84.8 kB). View file
 
code/data/fonts/DeliusUnicase-Regular.ttf ADDED
Binary file (31.5 kB). View file
 
code/data/fonts/HobeauxRococeaux-Sherman.ttf ADDED
Binary file (117 kB). View file
 
code/data/fonts/IndieFlower-Regular.ttf ADDED
Binary file (55.4 kB). View file
 
code/data/fonts/JosefinSans-Light.ttf ADDED
Binary file (59.3 kB). View file
 
code/data/fonts/KaushanScript-Regular.ttf ADDED
Binary file (184 kB). View file
 
code/data/fonts/LuckiestGuy-Regular.ttf ADDED
Binary file (58.3 kB). View file
 
code/data/fonts/Noteworthy-Bold.ttf ADDED
Binary file (248 kB). View file
 
code/data/fonts/Quicksand.ttf ADDED
Binary file (124 kB). View file
 
code/data/fonts/Saira-Regular.ttf ADDED
Binary file (82.8 kB). View file
 
code/losses.py ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch.nn as nn
2
+ import torchvision
3
+ from scipy.spatial import Delaunay
4
+ import torch
5
+ import numpy as np
6
+ from torch.nn import functional as nnf
7
+ from easydict import EasyDict
8
+ from shapely.geometry import Point
9
+ from shapely.geometry.polygon import Polygon
10
+
11
+ from diffusers import StableDiffusionPipeline
12
+
13
+ class SDSLoss(nn.Module):
14
+ def __init__(self, cfg, device, model):
15
+ super(SDSLoss, self).__init__()
16
+ self.cfg = cfg
17
+ self.device = device
18
+ # self.pipe = StableDiffusionPipeline.from_pretrained(cfg.diffusion.model,
19
+ # torch_dtype=torch.float16, use_auth_token=cfg.token)
20
+
21
+ # self.pipe = self.pipe.to(self.device)
22
+ self.pipe=model
23
+ # default scheduler: PNDMScheduler(beta_start=0.00085, beta_end=0.012,
24
+ # beta_schedule="scaled_linear", num_train_timesteps=1000)
25
+ self.alphas = self.pipe.scheduler.alphas_cumprod.to(self.device)
26
+ self.sigmas = (1 - self.pipe.scheduler.alphas_cumprod).to(self.device)
27
+
28
+ self.text_embeddings = None
29
+ self.embed_text()
30
+
31
+ def embed_text(self):
32
+ # tokenizer and embed text
33
+ text_input = self.pipe.tokenizer(self.cfg.caption, padding="max_length",
34
+ max_length=self.pipe.tokenizer.model_max_length,
35
+ truncation=True, return_tensors="pt")
36
+ uncond_input = self.pipe.tokenizer([""], padding="max_length",
37
+ max_length=text_input.input_ids.shape[-1],
38
+ return_tensors="pt")
39
+ with torch.no_grad():
40
+ text_embeddings = self.pipe.text_encoder(text_input.input_ids.to(self.device))[0]
41
+ uncond_embeddings = self.pipe.text_encoder(uncond_input.input_ids.to(self.device))[0]
42
+ self.text_embeddings = torch.cat([uncond_embeddings, text_embeddings])
43
+ self.text_embeddings = self.text_embeddings.repeat_interleave(self.cfg.batch_size, 0)
44
+ # del self.pipe.tokenizer
45
+ # del self.pipe.text_encoder
46
+
47
+
48
+ def forward(self, x_aug):
49
+ sds_loss = 0
50
+
51
+ # encode rendered image
52
+ x = x_aug * 2. - 1.
53
+ with torch.cuda.amp.autocast():
54
+ init_latent_z = (self.pipe.vae.encode(x).latent_dist.sample())
55
+ latent_z = 0.18215 * init_latent_z # scaling_factor * init_latents
56
+
57
+ with torch.inference_mode():
58
+ # sample timesteps
59
+ timestep = torch.randint(
60
+ low=50,
61
+ high=min(950, self.cfg.diffusion.timesteps) - 1, # avoid highest timestep | diffusion.timesteps=1000
62
+ size=(latent_z.shape[0],),
63
+ device=self.device, dtype=torch.long)
64
+
65
+ # add noise
66
+ eps = torch.randn_like(latent_z)
67
+ # zt = alpha_t * latent_z + sigma_t * eps
68
+ noised_latent_zt = self.pipe.scheduler.add_noise(latent_z, eps, timestep)
69
+
70
+ # denoise
71
+ z_in = torch.cat([noised_latent_zt] * 2) # expand latents for classifier free guidance
72
+ timestep_in = torch.cat([timestep] * 2)
73
+ with torch.autocast(device_type="cuda", dtype=torch.float16):
74
+ eps_t_uncond, eps_t = self.pipe.unet(z_in, timestep, encoder_hidden_states=self.text_embeddings).sample.float().chunk(2)
75
+
76
+ eps_t = eps_t_uncond + self.cfg.diffusion.guidance_scale * (eps_t - eps_t_uncond)
77
+
78
+ # w = alphas[timestep]^0.5 * (1 - alphas[timestep]) = alphas[timestep]^0.5 * sigmas[timestep]
79
+ grad_z = self.alphas[timestep]**0.5 * self.sigmas[timestep] * (eps_t - eps)
80
+ assert torch.isfinite(grad_z).all()
81
+ grad_z = torch.nan_to_num(grad_z.detach().float(), 0.0, 0.0, 0.0)
82
+
83
+ sds_loss = grad_z.clone() * latent_z
84
+ del grad_z
85
+
86
+ sds_loss = sds_loss.sum(1).mean()
87
+ return sds_loss
88
+
89
+
90
+ class ToneLoss(nn.Module):
91
+ def __init__(self, cfg):
92
+ super(ToneLoss, self).__init__()
93
+ self.dist_loss_weight = cfg.loss.tone.dist_loss_weight
94
+ self.im_init = None
95
+ self.cfg = cfg
96
+ self.mse_loss = nn.MSELoss()
97
+ self.blurrer = torchvision.transforms.GaussianBlur(kernel_size=(cfg.loss.tone.pixel_dist_kernel_blur,
98
+ cfg.loss.tone.pixel_dist_kernel_blur), sigma=(cfg.loss.tone.pixel_dist_sigma))
99
+
100
+ def set_image_init(self, im_init):
101
+ self.im_init = im_init.permute(2, 0, 1).unsqueeze(0)
102
+ self.init_blurred = self.blurrer(self.im_init)
103
+
104
+
105
+ def get_scheduler(self, step=None):
106
+ if step is not None:
107
+ return self.dist_loss_weight * np.exp(-(1/5)*((step-300)/(20)) ** 2)
108
+ else:
109
+ return self.dist_loss_weight
110
+
111
+ def forward(self, cur_raster, step=None):
112
+ blurred_cur = self.blurrer(cur_raster)
113
+ return self.mse_loss(self.init_blurred.detach(), blurred_cur) * self.get_scheduler(step)
114
+
115
+
116
+ class ConformalLoss:
117
+ def __init__(self, parameters: EasyDict, device: torch.device, target_letter: str, shape_groups):
118
+ self.parameters = parameters
119
+ self.target_letter = target_letter
120
+ self.shape_groups = shape_groups
121
+ self.faces = self.init_faces(device)
122
+ self.faces_roll_a = [torch.roll(self.faces[i], 1, 1) for i in range(len(self.faces))]
123
+
124
+ with torch.no_grad():
125
+ self.angles = []
126
+ self.reset()
127
+
128
+
129
+ def get_angles(self, points: torch.Tensor) -> torch.Tensor:
130
+ angles_ = []
131
+ for i in range(len(self.faces)):
132
+ triangles = points[self.faces[i]]
133
+ triangles_roll_a = points[self.faces_roll_a[i]]
134
+ edges = triangles_roll_a - triangles
135
+ length = edges.norm(dim=-1)
136
+ edges = edges / (length + 1e-1)[:, :, None]
137
+ edges_roll = torch.roll(edges, 1, 1)
138
+ cosine = torch.einsum('ned,ned->ne', edges, edges_roll)
139
+ angles = torch.arccos(cosine)
140
+ angles_.append(angles)
141
+ return angles_
142
+
143
+ def get_letter_inds(self, letter_to_insert):
144
+ for group, l in zip(self.shape_groups, self.target_letter):
145
+ if l == letter_to_insert:
146
+ letter_inds = group.shape_ids
147
+ return letter_inds[0], letter_inds[-1], len(letter_inds)
148
+
149
+ def reset(self):
150
+ points = torch.cat([point.clone().detach() for point in self.parameters.point])
151
+ self.angles = self.get_angles(points)
152
+
153
+ def init_faces(self, device: torch.device) -> torch.tensor:
154
+ faces_ = []
155
+ for j, c in enumerate(self.target_letter):
156
+ points_np = [self.parameters.point[i].clone().detach().cpu().numpy() for i in range(len(self.parameters.point))]
157
+ start_ind, end_ind, shapes_per_letter = self.get_letter_inds(c)
158
+ print(c, start_ind, end_ind)
159
+ holes = []
160
+ if shapes_per_letter > 1:
161
+ holes = points_np[start_ind+1:end_ind]
162
+ poly = Polygon(points_np[start_ind], holes=holes)
163
+ poly = poly.buffer(0)
164
+ points_np = np.concatenate(points_np)
165
+ faces = Delaunay(points_np).simplices
166
+ is_intersect = np.array([poly.contains(Point(points_np[face].mean(0))) for face in faces], dtype=np.bool)
167
+ faces_.append(torch.from_numpy(faces[is_intersect]).to(device, dtype=torch.int64))
168
+ return faces_
169
+
170
+ def __call__(self) -> torch.Tensor:
171
+ loss_angles = 0
172
+ points = torch.cat(self.parameters.point)
173
+ angles = self.get_angles(points)
174
+ for i in range(len(self.faces)):
175
+ loss_angles += (nnf.mse_loss(angles[i], self.angles[i]))
176
+ return loss_angles
177
+
178
+
179
+
180
+
code/save_svg.py ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ import pydiffvg
3
+ import xml.etree.ElementTree as etree
4
+ from xml.dom import minidom
5
+ def prettify(elem):
6
+ """Return a pretty-printed XML string for the Element.
7
+ """
8
+ rough_string = etree.tostring(elem, 'utf-8')
9
+ reparsed = minidom.parseString(rough_string)
10
+ return reparsed.toprettyxml(indent=" ")
11
+ def save_svg(filename, width, height, shapes, shape_groups, use_gamma = False, background=None):
12
+ root = etree.Element('svg')
13
+ root.set('version', '1.1')
14
+ root.set('xmlns', 'http://www.w3.org/2000/svg')
15
+ root.set('width', str(width))
16
+ root.set('height', str(height))
17
+ if background is not None:
18
+ print(f"setting background to {background}")
19
+ root.set('style', str(background))
20
+ defs = etree.SubElement(root, 'defs')
21
+ g = etree.SubElement(root, 'g')
22
+ if use_gamma:
23
+ f = etree.SubElement(defs, 'filter')
24
+ f.set('id', 'gamma')
25
+ f.set('x', '0')
26
+ f.set('y', '0')
27
+ f.set('width', '100%')
28
+ f.set('height', '100%')
29
+ gamma = etree.SubElement(f, 'feComponentTransfer')
30
+ gamma.set('color-interpolation-filters', 'sRGB')
31
+ feFuncR = etree.SubElement(gamma, 'feFuncR')
32
+ feFuncR.set('type', 'gamma')
33
+ feFuncR.set('amplitude', str(1))
34
+ feFuncR.set('exponent', str(1/2.2))
35
+ feFuncG = etree.SubElement(gamma, 'feFuncG')
36
+ feFuncG.set('type', 'gamma')
37
+ feFuncG.set('amplitude', str(1))
38
+ feFuncG.set('exponent', str(1/2.2))
39
+ feFuncB = etree.SubElement(gamma, 'feFuncB')
40
+ feFuncB.set('type', 'gamma')
41
+ feFuncB.set('amplitude', str(1))
42
+ feFuncB.set('exponent', str(1/2.2))
43
+ feFuncA = etree.SubElement(gamma, 'feFuncA')
44
+ feFuncA.set('type', 'gamma')
45
+ feFuncA.set('amplitude', str(1))
46
+ feFuncA.set('exponent', str(1/2.2))
47
+ g.set('style', 'filter:url(#gamma)')
48
+ # Store color
49
+ for i, shape_group in enumerate(shape_groups):
50
+ def add_color(shape_color, name):
51
+ if isinstance(shape_color, pydiffvg.LinearGradient):
52
+ lg = shape_color
53
+ color = etree.SubElement(defs, 'linearGradient')
54
+ color.set('id', name)
55
+ color.set('x1', str(lg.begin[0].item()/width))
56
+ color.set('y1', str(lg.begin[1].item()/height))
57
+ color.set('x2', str(lg.end[0].item()/width))
58
+ color.set('y2', str(lg.end[1].item()/height))
59
+ offsets = lg.offsets.data.cpu().numpy()
60
+ stop_colors = lg.stop_colors.data.cpu().numpy()
61
+ for j in range(offsets.shape[0]):
62
+ stop = etree.SubElement(color, 'stop')
63
+ stop.set('offset', str(offsets[j]))
64
+ c = lg.stop_colors[j, :]
65
+ stop.set('stop-color', 'rgb({}, {}, {})'.format(\
66
+ int(255 * c[0]), int(255 * c[1]), int(255 * c[2])))
67
+ stop.set('stop-opacity', '{}'.format(c[3]))
68
+ if isinstance(shape_color, pydiffvg.RadialGradient):
69
+ lg = shape_color
70
+ color = etree.SubElement(defs, 'radialGradient')
71
+ color.set('id', name)
72
+ color.set('cx', str(lg.center[0].item()/width))
73
+ color.set('cy', str(lg.center[1].item()/height))
74
+ # this only support width=height
75
+ color.set('r', str(lg.radius[0].item()/width))
76
+ offsets = lg.offsets.data.cpu().numpy()
77
+ stop_colors = lg.stop_colors.data.cpu().numpy()
78
+ for j in range(offsets.shape[0]):
79
+ stop = etree.SubElement(color, 'stop')
80
+ stop.set('offset', str(offsets[j]))
81
+ c = lg.stop_colors[j, :]
82
+ stop.set('stop-color', 'rgb({}, {}, {})'.format(\
83
+ int(255 * c[0]), int(255 * c[1]), int(255 * c[2])))
84
+ stop.set('stop-opacity', '{}'.format(c[3]))
85
+ if shape_group.fill_color is not None:
86
+ add_color(shape_group.fill_color, 'shape_{}_fill'.format(i))
87
+ if shape_group.stroke_color is not None:
88
+ add_color(shape_group.stroke_color, 'shape_{}_stroke'.format(i))
89
+ for i, shape_group in enumerate(shape_groups):
90
+ # shape = shapes[shape_group.shape_ids[0]]
91
+ for j,id in enumerate(shape_group.shape_ids):
92
+ shape = shapes[id]
93
+ if isinstance(shape, pydiffvg.Path):
94
+ if j == 0:
95
+ shape_node = etree.SubElement(g, 'path')
96
+ path_str = ''
97
+ # shape_node = etree.SubElement(g, 'path')
98
+ num_segments = shape.num_control_points.shape[0]
99
+ num_control_points = shape.num_control_points.data.cpu().numpy()
100
+ points = shape.points.data.cpu().numpy()
101
+ num_points = shape.points.shape[0]
102
+ path_str += 'M {} {}'.format(points[0, 0], points[0, 1])
103
+ point_id = 1
104
+ for j in range(0, num_segments):
105
+ if num_control_points[j] == 0:
106
+ p = point_id % num_points
107
+ path_str += ' L {} {}'.format(\
108
+ points[p, 0], points[p, 1])
109
+ point_id += 1
110
+ elif num_control_points[j] == 1:
111
+ p1 = (point_id + 1) % num_points
112
+ path_str += ' Q {} {} {} {}'.format(\
113
+ points[point_id, 0], points[point_id, 1],
114
+ points[p1, 0], points[p1, 1])
115
+ point_id += 2
116
+ elif num_control_points[j] == 2:
117
+ p2 = (point_id + 2) % num_points
118
+ path_str += ' C {} {} {} {} {} {}'.format(\
119
+ points[point_id, 0], points[point_id, 1],
120
+ points[point_id + 1, 0], points[point_id + 1, 1],
121
+ points[p2, 0], points[p2, 1])
122
+ point_id += 3
123
+ else:
124
+ assert(False)
125
+ # shape_node.set('stroke-width', str(2 * shape.stroke_width.data.cpu().item()))
126
+ shape_node.set('stroke-width', str(0)) # no strokes
127
+ if shape_group.fill_color is not None:
128
+ if isinstance(shape_group.fill_color, pydiffvg.LinearGradient):
129
+ shape_node.set('fill', 'url(#shape_{}_fill)'.format(i))
130
+ elif isinstance(shape_group.fill_color, pydiffvg.RadialGradient):
131
+ shape_node.set('fill', 'url(#shape_{}_fill)'.format(i))
132
+ else:
133
+ c = shape_group.fill_color.data.cpu().numpy()
134
+ shape_node.set('fill', 'rgb({}, {}, {})'.format(\
135
+ int(255 * c[0]), int(255 * c[1]), int(255 * c[2])))
136
+ shape_node.set('opacity', str(c[3]))
137
+ else:
138
+ shape_node.set('fill', 'none')
139
+ if shape_group.stroke_color is not None:
140
+ if isinstance(shape_group.stroke_color, pydiffvg.LinearGradient):
141
+ shape_node.set('stroke', 'url(#shape_{}_stroke)'.format(i))
142
+ elif isinstance(shape_group.stroke_color, pydiffvg.LinearGradient):
143
+ shape_node.set('stroke', 'url(#shape_{}_stroke)'.format(i))
144
+ else:
145
+ c = shape_group.stroke_color.data.cpu().numpy()
146
+ shape_node.set('stroke', 'rgb({}, {}, {})'.format(\
147
+ int(255 * c[0]), int(255 * c[1]), int(255 * c[2])))
148
+ shape_node.set('stroke-opacity', str(c[3]))
149
+ shape_node.set('stroke-linecap', 'round')
150
+ shape_node.set('stroke-linejoin', 'round')
151
+
152
+ shape_node.set('d', path_str)
153
+
154
+ with open(filename, "w") as f:
155
+ f.write(prettify(root))
code/ttf.py ADDED
@@ -0,0 +1,264 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from importlib import reload
2
+ import os
3
+ import numpy as np
4
+ import bezier
5
+ import freetype as ft
6
+ import pydiffvg
7
+ import torch
8
+ import save_svg
9
+ import os.path as osp
10
+
11
+ device = torch.device("cuda" if (
12
+ torch.cuda.is_available() and torch.cuda.device_count() > 0) else "cpu")
13
+
14
+ reload(bezier)
15
+
16
+ def fix_single_svg(svg_path, all_word=False):
17
+ target_h_letter = 360
18
+ target_canvas_width, target_canvas_height = 600, 600
19
+
20
+ canvas_width, canvas_height, shapes, shape_groups = pydiffvg.svg_to_scene(svg_path)
21
+
22
+ letter_h = canvas_height
23
+ letter_w = canvas_width
24
+
25
+ if all_word:
26
+ if letter_w > letter_h:
27
+ scale_canvas_w = target_h_letter / letter_w
28
+ hsize = int(letter_h * scale_canvas_w)
29
+ scale_canvas_h = hsize / letter_h
30
+ else:
31
+ scale_canvas_h = target_h_letter / letter_h
32
+ wsize = int(letter_w * scale_canvas_h)
33
+ scale_canvas_w = wsize / letter_w
34
+ else:
35
+ scale_canvas_h = target_h_letter / letter_h
36
+ wsize = int(letter_w * scale_canvas_h)
37
+ scale_canvas_w = wsize / letter_w
38
+
39
+ for num, p in enumerate(shapes):
40
+ p.points[:, 0] = p.points[:, 0] * scale_canvas_w
41
+ p.points[:, 1] = p.points[:, 1] * scale_canvas_h + target_h_letter
42
+
43
+ w_min, w_max = min([torch.min(p.points[:, 0]) for p in shapes]), max([torch.max(p.points[:, 0]) for p in shapes])
44
+ h_min, h_max = min([torch.min(p.points[:, 1]) for p in shapes]), max([torch.max(p.points[:, 1]) for p in shapes])
45
+
46
+ for num, p in enumerate(shapes):
47
+ p.points[:, 0] = p.points[:, 0] + target_canvas_width/2 - int(w_min + (w_max - w_min) / 2)
48
+ p.points[:, 1] = p.points[:, 1] + target_canvas_height/2 - int(h_min + (h_max - h_min) / 2)
49
+
50
+ output_path = f"{svg_path[:-4]}_scaled.svg"
51
+ save_svg.save_svg(output_path, target_canvas_width, target_canvas_height, shapes, shape_groups)
52
+
53
+
54
+ def normalize_letter_size(dest_path, font, txt):
55
+ fontname = os.path.splitext(os.path.basename(font))[0]
56
+ for i, c in enumerate(txt):
57
+ fname = f"{dest_path}/{fontname}_{c}.svg"
58
+ fname = fname.replace(" ", "_")
59
+ fix_single_svg(fname)
60
+
61
+ fname = f"{dest_path}/{fontname}_{txt}.svg"
62
+ fname = fname.replace(" ", "_")
63
+ fix_single_svg(fname, all_word=True)
64
+
65
+
66
+ def glyph_to_cubics(face, x=0):
67
+ ''' Convert current font face glyph to cubic beziers'''
68
+
69
+ def linear_to_cubic(Q):
70
+ a, b = Q
71
+ return [a + (b - a) * t for t in np.linspace(0, 1, 4)]
72
+
73
+ def quadratic_to_cubic(Q):
74
+ return [Q[0],
75
+ Q[0] + (2 / 3) * (Q[1] - Q[0]),
76
+ Q[2] + (2 / 3) * (Q[1] - Q[2]),
77
+ Q[2]]
78
+
79
+ beziers = []
80
+ pt = lambda p: np.array([p.x + x, -p.y]) # Flipping here since freetype has y-up
81
+ last = lambda: beziers[-1][-1]
82
+
83
+ def move_to(a, beziers):
84
+ beziers.append([pt(a)])
85
+
86
+ def line_to(a, beziers):
87
+ Q = linear_to_cubic([last(), pt(a)])
88
+ beziers[-1] += Q[1:]
89
+
90
+ def conic_to(a, b, beziers):
91
+ Q = quadratic_to_cubic([last(), pt(a), pt(b)])
92
+ beziers[-1] += Q[1:]
93
+
94
+ def cubic_to(a, b, c, beziers):
95
+ beziers[-1] += [pt(a), pt(b), pt(c)]
96
+
97
+ face.glyph.outline.decompose(beziers, move_to=move_to, line_to=line_to, conic_to=conic_to, cubic_to=cubic_to)
98
+ beziers = [np.array(C).astype(float) for C in beziers]
99
+ return beziers
100
+
101
+
102
+ def font_string_to_beziers(font, txt, size=30, spacing=1.0, merge=True, target_control=None):
103
+ ''' Load a font and convert the outlines for a given string to cubic bezier curves,
104
+ if merge is True, simply return a list of all bezier curves,
105
+ otherwise return a list of lists with the bezier curves for each glyph'''
106
+
107
+ face = ft.Face(font)
108
+ face.set_char_size(64 * size)
109
+ slot = face.glyph
110
+
111
+ x = 0
112
+ beziers = []
113
+ previous = 0
114
+ for c in txt:
115
+ face.load_char(c, ft.FT_LOAD_DEFAULT | ft.FT_LOAD_NO_BITMAP)
116
+ bez = glyph_to_cubics(face, x)
117
+
118
+ # Check number of control points if desired
119
+ if target_control is not None:
120
+ if c in target_control.keys():
121
+ nctrl = np.sum([len(C) for C in bez])
122
+ while nctrl < target_control[c]:
123
+ longest = np.max(
124
+ sum([[bezier.approx_arc_length(b) for b in bezier.chain_to_beziers(C)] for C in bez], []))
125
+ thresh = longest * 0.5
126
+ bez = [bezier.subdivide_bezier_chain(C, thresh) for C in bez]
127
+ nctrl = np.sum([len(C) for C in bez])
128
+ print(nctrl)
129
+
130
+ if merge:
131
+ beziers += bez
132
+ else:
133
+ beziers.append(bez)
134
+
135
+ kerning = face.get_kerning(previous, c)
136
+ x += (slot.advance.x + kerning.x) * spacing
137
+ previous = c
138
+
139
+ return beziers
140
+
141
+
142
+ def bezier_chain_to_commands(C, closed=True):
143
+ curves = bezier.chain_to_beziers(C)
144
+ cmds = 'M %f %f ' % (C[0][0], C[0][1])
145
+ n = len(curves)
146
+ for i, bez in enumerate(curves):
147
+ if i == n - 1 and closed:
148
+ cmds += 'C %f %f %f %f %f %fz ' % (*bez[1], *bez[2], *bez[3])
149
+ else:
150
+ cmds += 'C %f %f %f %f %f %f ' % (*bez[1], *bez[2], *bez[3])
151
+ return cmds
152
+
153
+
154
+ def count_cp(file_name, font_name):
155
+ canvas_width, canvas_height, shapes, shape_groups = pydiffvg.svg_to_scene(file_name)
156
+ p_counter = 0
157
+ for path in shapes:
158
+ p_counter += path.points.shape[0]
159
+ print(f"TOTAL CP: [{p_counter}]")
160
+ return p_counter
161
+
162
+
163
+ def write_letter_svg(c, header, fontname, beziers, subdivision_thresh, dest_path):
164
+ cmds = ''
165
+ svg = header
166
+
167
+ path = '<g><path d="'
168
+ for C in beziers:
169
+ if subdivision_thresh is not None:
170
+ print('subd')
171
+ C = bezier.subdivide_bezier_chain(C, subdivision_thresh)
172
+ cmds += bezier_chain_to_commands(C, True)
173
+ path += cmds + '"/>\n'
174
+ svg += path + '</g></svg>\n'
175
+
176
+ fname = f"{dest_path}/{fontname}_{c}.svg"
177
+ fname = fname.replace(" ", "_")
178
+ f = open(fname, 'w')
179
+ f.write(svg)
180
+ f.close()
181
+ return fname, path
182
+
183
+
184
+ def font_string_to_svgs(dest_path, font, txt, size=30, spacing=1.0, target_control=None, subdivision_thresh=None):
185
+
186
+ fontname = os.path.splitext(os.path.basename(font))[0]
187
+ glyph_beziers = font_string_to_beziers(font, txt, size, spacing, merge=False, target_control=target_control)
188
+ if not os.path.isdir(dest_path):
189
+ os.mkdir(dest_path)
190
+ # Compute boundig box
191
+ points = np.vstack(sum(glyph_beziers, []))
192
+ lt = np.min(points, axis=0)
193
+ rb = np.max(points, axis=0)
194
+ size = rb - lt
195
+
196
+ sizestr = 'width="%.1f" height="%.1f"' % (size[0], size[1])
197
+ boxstr = ' viewBox="%.1f %.1f %.1f %.1f"' % (lt[0], lt[1], size[0], size[1])
198
+ header = '''<?xml version="1.0" encoding="utf-8"?>
199
+ <svg xmlns="http://www.w3.org/2000/svg" xmlns:ev="http://www.w3.org/2001/xml-events" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" '''
200
+ header += sizestr
201
+ header += boxstr
202
+ header += '>\n<defs/>\n'
203
+
204
+ svg_all = header
205
+
206
+ for i, (c, beziers) in enumerate(zip(txt, glyph_beziers)):
207
+ print(f"==== {c} ====")
208
+ fname, path = write_letter_svg(c, header, fontname, beziers, subdivision_thresh, dest_path)
209
+
210
+ num_cp = count_cp(fname, fontname)
211
+ print(num_cp)
212
+ print(font, c)
213
+ # Add to global svg
214
+ svg_all += path + '</g>\n'
215
+
216
+ # Save global svg
217
+ svg_all += '</svg>\n'
218
+ fname = f"{dest_path}/{fontname}_{txt}.svg"
219
+ fname = fname.replace(" ", "_")
220
+ f = open(fname, 'w')
221
+ f.write(svg_all)
222
+ f.close()
223
+
224
+
225
+
226
+
227
+ if __name__ == '__main__':
228
+
229
+ fonts = ["KaushanScript-Regular"]
230
+ level_of_cc = 1
231
+
232
+ if level_of_cc == 0:
233
+ target_cp = None
234
+
235
+ else:
236
+ target_cp = {"A": 120, "B": 120, "C": 100, "D": 100,
237
+ "E": 120, "F": 120, "G": 120, "H": 120,
238
+ "I": 35, "J": 80, "K": 100, "L": 80,
239
+ "M": 100, "N": 100, "O": 100, "P": 120,
240
+ "Q": 120, "R": 130, "S": 110, "T": 90,
241
+ "U": 100, "V": 100, "W": 100, "X": 130,
242
+ "Y": 120, "Z": 120,
243
+ "a": 120, "b": 120, "c": 100, "d": 100,
244
+ "e": 120, "f": 120, "g": 120, "h": 120,
245
+ "i": 35, "j": 80, "k": 100, "l": 80,
246
+ "m": 100, "n": 100, "o": 100, "p": 120,
247
+ "q": 120, "r": 130, "s": 110, "t": 90,
248
+ "u": 100, "v": 100, "w": 100, "x": 130,
249
+ "y": 120, "z": 120
250
+ }
251
+
252
+ target_cp = {k: v * level_of_cc for k, v in target_cp.items()}
253
+
254
+ for f in fonts:
255
+ print(f"======= {f} =======")
256
+ font_path = f"data/fonts/{f}.ttf"
257
+ output_path = f"data/init"
258
+ txt = "BUNNY"
259
+ subdivision_thresh = None
260
+ font_string_to_svgs(output_path, font_path, txt, target_control=target_cp,
261
+ subdivision_thresh=subdivision_thresh)
262
+ normalize_letter_size(output_path, font_path, txt)
263
+
264
+ print("DONE")
code/utils.py ADDED
@@ -0,0 +1,221 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import collections.abc
2
+ import os
3
+ import os.path as osp
4
+ from torch import nn
5
+ import kornia.augmentation as K
6
+ import pydiffvg
7
+ import save_svg
8
+ import cv2
9
+ from ttf import font_string_to_svgs, normalize_letter_size
10
+ import torch
11
+ import numpy as np
12
+
13
+
14
+ def edict_2_dict(x):
15
+ if isinstance(x, dict):
16
+ xnew = {}
17
+ for k in x:
18
+ xnew[k] = edict_2_dict(x[k])
19
+ return xnew
20
+ elif isinstance(x, list):
21
+ xnew = []
22
+ for i in range(len(x)):
23
+ xnew.append( edict_2_dict(x[i]))
24
+ return xnew
25
+ else:
26
+ return x
27
+
28
+
29
+ def check_and_create_dir(path):
30
+ pathdir = osp.split(path)[0]
31
+ if osp.isdir(pathdir):
32
+ pass
33
+ else:
34
+ os.makedirs(pathdir)
35
+
36
+
37
+ def update(d, u):
38
+ """https://stackoverflow.com/questions/3232943/update-value-of-a-nested-dictionary-of-varying-depth"""
39
+ for k, v in u.items():
40
+ if isinstance(v, collections.abc.Mapping):
41
+ d[k] = update(d.get(k, {}), v)
42
+ else:
43
+ d[k] = v
44
+ return d
45
+
46
+
47
+ def preprocess(font, word, letter, level_of_cc=1):
48
+
49
+ if level_of_cc == 0:
50
+ target_cp = None
51
+ else:
52
+ target_cp = {"A": 120, "B": 120, "C": 100, "D": 100,
53
+ "E": 120, "F": 120, "G": 120, "H": 120,
54
+ "I": 35, "J": 80, "K": 100, "L": 80,
55
+ "M": 100, "N": 100, "O": 100, "P": 120,
56
+ "Q": 120, "R": 130, "S": 110, "T": 90,
57
+ "U": 100, "V": 100, "W": 100, "X": 130,
58
+ "Y": 120, "Z": 120,
59
+ "a": 120, "b": 120, "c": 100, "d": 100,
60
+ "e": 120, "f": 120, "g": 120, "h": 120,
61
+ "i": 35, "j": 80, "k": 100, "l": 80,
62
+ "m": 100, "n": 100, "o": 100, "p": 120,
63
+ "q": 120, "r": 130, "s": 110, "t": 90,
64
+ "u": 100, "v": 100, "w": 100, "x": 130,
65
+ "y": 120, "z": 120
66
+ }
67
+ target_cp = {k: v * level_of_cc for k, v in target_cp.items()}
68
+
69
+ print(f"======= {font} =======")
70
+ font_path = f"code/data/fonts/{font}.ttf"
71
+ init_path = f"code/data/init"
72
+ subdivision_thresh = None
73
+ font_string_to_svgs(init_path, font_path, word, target_control=target_cp,
74
+ subdivision_thresh=subdivision_thresh)
75
+ normalize_letter_size(init_path, font_path, word)
76
+
77
+ # optimaize two adjacent letters
78
+ if len(letter) > 1:
79
+ subdivision_thresh = None
80
+ font_string_to_svgs(init_path, font_path, letter, target_control=target_cp,
81
+ subdivision_thresh=subdivision_thresh)
82
+ normalize_letter_size(init_path, font_path, letter)
83
+
84
+ print("Done preprocess")
85
+
86
+
87
+ def get_data_augs(cut_size):
88
+ augmentations = []
89
+ augmentations.append(K.RandomPerspective(distortion_scale=0.5, p=0.7))
90
+ augmentations.append(K.RandomCrop(size=(cut_size, cut_size), pad_if_needed=True, padding_mode='reflect', p=1.0))
91
+ return nn.Sequential(*augmentations)
92
+
93
+
94
+ '''pytorch adaptation of https://github.com/google/mipnerf'''
95
+ def learning_rate_decay(step,
96
+ lr_init,
97
+ lr_final,
98
+ max_steps,
99
+ lr_delay_steps=0,
100
+ lr_delay_mult=1):
101
+ """Continuous learning rate decay function.
102
+ The returned rate is lr_init when step=0 and lr_final when step=max_steps, and
103
+ is log-linearly interpolated elsewhere (equivalent to exponential decay).
104
+ If lr_delay_steps>0 then the learning rate will be scaled by some smooth
105
+ function of lr_delay_mult, such that the initial learning rate is
106
+ lr_init*lr_delay_mult at the beginning of optimization but will be eased back
107
+ to the normal learning rate when steps>lr_delay_steps.
108
+ Args:
109
+ step: int, the current optimization step.
110
+ lr_init: float, the initial learning rate.
111
+ lr_final: float, the final learning rate.
112
+ max_steps: int, the number of steps during optimization.
113
+ lr_delay_steps: int, the number of steps to delay the full learning rate.
114
+ lr_delay_mult: float, the multiplier on the rate when delaying it.
115
+ Returns:
116
+ lr: the learning for current step 'step'.
117
+ """
118
+ if lr_delay_steps > 0:
119
+ # A kind of reverse cosine decay.
120
+ delay_rate = lr_delay_mult + (1 - lr_delay_mult) * np.sin(
121
+ 0.5 * np.pi * np.clip(step / lr_delay_steps, 0, 1))
122
+ else:
123
+ delay_rate = 1.
124
+ t = np.clip(step / max_steps, 0, 1)
125
+ log_lerp = np.exp(np.log(lr_init) * (1 - t) + np.log(lr_final) * t)
126
+ return delay_rate * log_lerp
127
+
128
+
129
+
130
+ def save_image(img, filename, gamma=1):
131
+ check_and_create_dir(filename)
132
+ imshow = img.detach().cpu()
133
+ pydiffvg.imwrite(imshow, filename, gamma=gamma)
134
+
135
+
136
+ def get_letter_ids(letter, word, shape_groups):
137
+ for group, l in zip(shape_groups, word):
138
+ if l == letter:
139
+ return group.shape_ids
140
+
141
+
142
+ def combine_word(word, letter, font, experiment_dir):
143
+ word_svg_scaled = f"./code/data/init/{font}_{word}_scaled.svg"
144
+ canvas_width_word, canvas_height_word, shapes_word, shape_groups_word = pydiffvg.svg_to_scene(word_svg_scaled)
145
+ letter_ids = []
146
+ for l in letter:
147
+ letter_ids += get_letter_ids(l, word, shape_groups_word)
148
+
149
+ w_min, w_max = min([torch.min(shapes_word[ids].points[:, 0]) for ids in letter_ids]), max(
150
+ [torch.max(shapes_word[ids].points[:, 0]) for ids in letter_ids])
151
+ h_min, h_max = min([torch.min(shapes_word[ids].points[:, 1]) for ids in letter_ids]), max(
152
+ [torch.max(shapes_word[ids].points[:, 1]) for ids in letter_ids])
153
+
154
+ c_w = (-w_min+w_max)/2
155
+ c_h = (-h_min+h_max)/2
156
+
157
+ svg_result = os.path.join(experiment_dir, "output-svg", "output.svg")
158
+ canvas_width, canvas_height, shapes, shape_groups = pydiffvg.svg_to_scene(svg_result)
159
+
160
+ out_w_min, out_w_max = min([torch.min(p.points[:, 0]) for p in shapes]), max(
161
+ [torch.max(p.points[:, 0]) for p in shapes])
162
+ out_h_min, out_h_max = min([torch.min(p.points[:, 1]) for p in shapes]), max(
163
+ [torch.max(p.points[:, 1]) for p in shapes])
164
+
165
+ out_c_w = (-out_w_min+out_w_max)/2
166
+ out_c_h = (-out_h_min+out_h_max)/2
167
+
168
+ scale_canvas_w = (w_max - w_min) / (out_w_max - out_w_min)
169
+ scale_canvas_h = (h_max - h_min) / (out_h_max - out_h_min)
170
+
171
+ if scale_canvas_h > scale_canvas_w:
172
+ wsize = int((out_w_max - out_w_min) * scale_canvas_h)
173
+ scale_canvas_w = wsize / (out_w_max - out_w_min)
174
+ shift_w = -out_c_w * scale_canvas_w + c_w
175
+ else:
176
+ hsize = int((out_h_max - out_h_min) * scale_canvas_w)
177
+ scale_canvas_h = hsize / (out_h_max - out_h_min)
178
+ shift_h = -out_c_h * scale_canvas_h + c_h
179
+
180
+ for num, p in enumerate(shapes):
181
+ p.points[:, 0] = p.points[:, 0] * scale_canvas_w
182
+ p.points[:, 1] = p.points[:, 1] * scale_canvas_h
183
+ if scale_canvas_h > scale_canvas_w:
184
+ p.points[:, 0] = p.points[:, 0] - out_w_min * scale_canvas_w + w_min + shift_w
185
+ p.points[:, 1] = p.points[:, 1] - out_h_min * scale_canvas_h + h_min
186
+ else:
187
+ p.points[:, 0] = p.points[:, 0] - out_w_min * scale_canvas_w + w_min
188
+ p.points[:, 1] = p.points[:, 1] - out_h_min * scale_canvas_h + h_min + shift_h
189
+
190
+
191
+ for j, s in enumerate(letter_ids):
192
+ shapes_word[s] = shapes[j]
193
+
194
+ save_svg.save_svg(
195
+ f"{experiment_dir}/{font}_{word}_{letter}.svg", canvas_width, canvas_height, shapes_word,
196
+ shape_groups_word)
197
+
198
+ # render = pydiffvg.RenderFunction.apply
199
+ # scene_args = pydiffvg.RenderFunction.serialize_scene(canvas_width, canvas_height, shapes_word, shape_groups_word)
200
+ # img = render(canvas_width, canvas_height, 2, 2, 0, None, *scene_args)
201
+ # img = img[:, :, 3:4] * img[:, :, :3] + \
202
+ # torch.ones(img.shape[0], img.shape[1], 3, device="cuda") * (1 - img[:, :, 3:4])
203
+ # img = img[:, :, :3]
204
+ # save_image(img, f"{experiment_dir}/{font}_{word}_{letter}.png")
205
+
206
+ def create_video(num_iter, experiment_dir, video_frame_freq):
207
+ img_array = []
208
+ for ii in range(0, num_iter):
209
+ if ii % video_frame_freq == 0 or ii == num_iter - 1:
210
+ filename = os.path.join(
211
+ experiment_dir, "video-png", f"iter{ii:04d}.png")
212
+ img = cv2.imread(filename)
213
+ img_array.append(img)
214
+
215
+ video_name = os.path.join(
216
+ experiment_dir, "video.mp4")
217
+ check_and_create_dir(video_name)
218
+ out = cv2.VideoWriter(video_name, cv2.VideoWriter_fourcc(*'mp4v'), 30.0, (600, 600))
219
+ for iii in range(len(img_array)):
220
+ out.write(img_array[iii])
221
+ out.release()
images/DeliusUnicase-Regular.png ADDED
images/HobeauxRococeaux-Sherman.png ADDED
images/IndieFlower-Regular.png ADDED
images/KaushanScript-Regular.png ADDED
images/LuckiestGuy-Regular.png ADDED
images/Noteworthy-Bold.png ADDED
images/Quicksand.png ADDED
images/Saira-Regular.png ADDED
packages.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ python3-dev
requirements.txt ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --extra-index-url https://download.pytorch.org/whl/cu113
2
+ torch==1.12.1+cu113
3
+ torchvision==0.13.1+cu113
4
+
5
+ cmake
6
+ numpy
7
+ scikit-image
8
+ ffmpeg
9
+ svgwrite
10
+ svgpathtools
11
+ cssutils
12
+ numba
13
+ torch-tools
14
+ scikit-fmm
15
+ easydict
16
+ visdom
17
+ opencv-python==4.5.4.60
18
+
19
+ diffusers==0.8
20
+ transformers
21
+ scipy
22
+ ftfy
23
+ accelerate
24
+
25
+ freetype-py
26
+ shapely
27
+ kornia==0.6.8