음성 재생과 운영 배포 정리
BIN
docs/avatar-art/seoyeon/live2d-psd-v2/app-render/render-grid.png
Normal file
|
After Width: | Height: | Size: 863 KiB |
|
After Width: | Height: | Size: 185 KiB |
|
After Width: | Height: | Size: 181 KiB |
|
After Width: | Height: | Size: 179 KiB |
|
After Width: | Height: | Size: 180 KiB |
BIN
docs/avatar-art/seoyeon/live2d-psd-v2/app-render/render-hero.png
Normal file
|
After Width: | Height: | Size: 676 KiB |
|
After Width: | Height: | Size: 443 KiB |
|
|
@ -0,0 +1,881 @@
|
|||
{
|
||||
"source": "D:\\workspace\\vignette\\docs\\avatar-art\\seoyeon\\live2d-psd-v2\\source-latudi-female-v2.psd",
|
||||
"sourceCanvas": [
|
||||
1080,
|
||||
1920
|
||||
],
|
||||
"targetCanvas": [
|
||||
900,
|
||||
1125
|
||||
],
|
||||
"cropBox": [
|
||||
0,
|
||||
240,
|
||||
1080,
|
||||
1590
|
||||
],
|
||||
"neutralLayerOrder": [
|
||||
"hair-back-base",
|
||||
"hair-back-left",
|
||||
"hair-back-right",
|
||||
"body",
|
||||
"outfit",
|
||||
"ear-left",
|
||||
"ear-right",
|
||||
"face-base",
|
||||
"blush-left",
|
||||
"blush-right",
|
||||
"eye-white-left",
|
||||
"eye-white-right",
|
||||
"iris-left",
|
||||
"iris-right",
|
||||
"pupil-left",
|
||||
"pupil-right",
|
||||
"highlight-left",
|
||||
"highlight-right",
|
||||
"lash-left",
|
||||
"lash-right",
|
||||
"eyelid-upper-left",
|
||||
"eyelid-upper-right",
|
||||
"eyelid-lower-left",
|
||||
"eyelid-lower-right",
|
||||
"brow-left",
|
||||
"brow-right",
|
||||
"nose",
|
||||
"hair-side-left-1",
|
||||
"hair-side-right-1",
|
||||
"hair-side-left-2",
|
||||
"hair-side-right-2",
|
||||
"hair-bangs",
|
||||
"mouth-neutral"
|
||||
],
|
||||
"sadLayerOrder": [
|
||||
"hair-back-base",
|
||||
"hair-back-left",
|
||||
"hair-back-right",
|
||||
"body",
|
||||
"outfit",
|
||||
"ear-left",
|
||||
"ear-right",
|
||||
"face-base",
|
||||
"blush-left",
|
||||
"blush-right",
|
||||
"eye-white-left",
|
||||
"eye-white-right",
|
||||
"iris-left",
|
||||
"iris-right",
|
||||
"pupil-left",
|
||||
"pupil-right",
|
||||
"highlight-left",
|
||||
"highlight-right",
|
||||
"lash-left",
|
||||
"lash-right",
|
||||
"eyelid-upper-left",
|
||||
"eyelid-upper-right",
|
||||
"eyelid-lower-left",
|
||||
"eyelid-lower-right",
|
||||
"brow-sad-left",
|
||||
"brow-sad-right",
|
||||
"tear-left",
|
||||
"tear-right",
|
||||
"nose",
|
||||
"hair-side-left-1",
|
||||
"hair-side-right-1",
|
||||
"hair-side-left-2",
|
||||
"hair-side-right-2",
|
||||
"hair-bangs",
|
||||
"mouth-sad"
|
||||
],
|
||||
"parts": [
|
||||
{
|
||||
"id": "hair-back-left",
|
||||
"source": "back hair left group",
|
||||
"file": "hair-back-left.png",
|
||||
"alphaBox": [
|
||||
0,
|
||||
0,
|
||||
463,
|
||||
1125
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 25.72,
|
||||
"y": 8.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "hair-back-right",
|
||||
"source": "back hair right group",
|
||||
"file": "hair-back-right.png",
|
||||
"alphaBox": [
|
||||
437,
|
||||
0,
|
||||
900,
|
||||
1125
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 74.28,
|
||||
"y": 8.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "body",
|
||||
"source": "body group",
|
||||
"file": "body.png",
|
||||
"alphaBox": [
|
||||
339,
|
||||
439,
|
||||
605,
|
||||
759
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 52.44,
|
||||
"y": 53.24
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "neck",
|
||||
"source": "neck group",
|
||||
"file": "neck.png",
|
||||
"alphaBox": [
|
||||
339,
|
||||
439,
|
||||
605,
|
||||
759
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 52.44,
|
||||
"y": 53.24
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "clavicle",
|
||||
"source": "clavicle group",
|
||||
"file": "clavicle.png",
|
||||
"alphaBox": [
|
||||
368,
|
||||
632,
|
||||
560,
|
||||
664
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 51.56,
|
||||
"y": 57.6
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "outfit",
|
||||
"source": "outfit group",
|
||||
"file": "outfit.png",
|
||||
"alphaBox": [
|
||||
130,
|
||||
521,
|
||||
806,
|
||||
1015
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 52.0,
|
||||
"y": 68.27
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "outfit-inner",
|
||||
"source": "inner outfit group",
|
||||
"file": "outfit-inner.png",
|
||||
"alphaBox": [
|
||||
232,
|
||||
521,
|
||||
705,
|
||||
1009
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 52.06,
|
||||
"y": 68.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "outfit-outer",
|
||||
"source": "outer outfit group",
|
||||
"file": "outfit-outer.png",
|
||||
"alphaBox": [
|
||||
130,
|
||||
541,
|
||||
806,
|
||||
1015
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 52.0,
|
||||
"y": 69.16
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ribbon",
|
||||
"source": "ribbon group",
|
||||
"file": "ribbon.png",
|
||||
"alphaBox": [
|
||||
368,
|
||||
636,
|
||||
570,
|
||||
832
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 52.11,
|
||||
"y": 65.24
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "face",
|
||||
"source": "face group",
|
||||
"file": "face.png",
|
||||
"alphaBox": [
|
||||
267,
|
||||
32,
|
||||
647,
|
||||
533
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 50.78,
|
||||
"y": 25.11
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "face-base",
|
||||
"source": "face base group",
|
||||
"file": "face-base.png",
|
||||
"alphaBox": [
|
||||
275,
|
||||
32,
|
||||
630,
|
||||
533
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 50.28,
|
||||
"y": 25.11
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ear-left",
|
||||
"source": "left ear group",
|
||||
"file": "ear-left.png",
|
||||
"alphaBox": [
|
||||
267,
|
||||
357,
|
||||
344,
|
||||
442
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 33.94,
|
||||
"y": 35.51
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ear-right",
|
||||
"source": "right ear group",
|
||||
"file": "ear-right.png",
|
||||
"alphaBox": [
|
||||
567,
|
||||
359,
|
||||
647,
|
||||
442
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 67.44,
|
||||
"y": 35.6
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "blush-left",
|
||||
"source": "left blush group",
|
||||
"file": "blush-left.png",
|
||||
"alphaBox": [
|
||||
297,
|
||||
337,
|
||||
445,
|
||||
444
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 41.22,
|
||||
"y": 34.71
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "blush-right",
|
||||
"source": "right blush group",
|
||||
"file": "blush-right.png",
|
||||
"alphaBox": [
|
||||
462,
|
||||
332,
|
||||
611,
|
||||
441
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 59.61,
|
||||
"y": 34.36
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "nose",
|
||||
"source": "nose group",
|
||||
"file": "nose.png",
|
||||
"alphaBox": [
|
||||
444,
|
||||
410,
|
||||
456,
|
||||
426
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 50.0,
|
||||
"y": 37.16
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "eye-left",
|
||||
"source": "left eye group",
|
||||
"file": "eye-left.png",
|
||||
"alphaBox": [
|
||||
293,
|
||||
314,
|
||||
419,
|
||||
389
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 39.56,
|
||||
"y": 31.24
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "eye-right",
|
||||
"source": "right eye group",
|
||||
"file": "eye-right.png",
|
||||
"alphaBox": [
|
||||
488,
|
||||
307,
|
||||
616,
|
||||
380
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 61.33,
|
||||
"y": 30.53
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "eye-white-left",
|
||||
"source": "left eye white group",
|
||||
"file": "eye-white-left.png",
|
||||
"alphaBox": [
|
||||
324,
|
||||
339,
|
||||
410,
|
||||
382
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 40.78,
|
||||
"y": 32.04
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "eye-white-right",
|
||||
"source": "right eye white group",
|
||||
"file": "eye-white-right.png",
|
||||
"alphaBox": [
|
||||
500,
|
||||
330,
|
||||
585,
|
||||
373
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 60.28,
|
||||
"y": 31.24
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "iris-left",
|
||||
"source": "left iris group",
|
||||
"file": "iris-left.png",
|
||||
"alphaBox": [
|
||||
346,
|
||||
331,
|
||||
401,
|
||||
382
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 41.5,
|
||||
"y": 31.69
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "iris-right",
|
||||
"source": "right iris group",
|
||||
"file": "iris-right.png",
|
||||
"alphaBox": [
|
||||
507,
|
||||
324,
|
||||
563,
|
||||
374
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 59.44,
|
||||
"y": 31.02
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "pupil-left",
|
||||
"source": "left pupil group",
|
||||
"file": "pupil-left.png",
|
||||
"alphaBox": [
|
||||
371,
|
||||
348,
|
||||
386,
|
||||
367
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 42.06,
|
||||
"y": 31.78
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "pupil-right",
|
||||
"source": "right pupil group",
|
||||
"file": "pupil-right.png",
|
||||
"alphaBox": [
|
||||
523,
|
||||
341,
|
||||
538,
|
||||
360
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 58.94,
|
||||
"y": 31.16
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "highlight-left",
|
||||
"source": "left eye highlight group",
|
||||
"file": "highlight-left.png",
|
||||
"alphaBox": [
|
||||
364,
|
||||
347,
|
||||
386,
|
||||
376
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 41.67,
|
||||
"y": 32.13
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "highlight-right",
|
||||
"source": "right eye highlight group",
|
||||
"file": "highlight-right.png",
|
||||
"alphaBox": [
|
||||
526,
|
||||
339,
|
||||
549,
|
||||
369
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 59.72,
|
||||
"y": 31.47
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "lash-left",
|
||||
"source": "left lash group",
|
||||
"file": "lash-left.png",
|
||||
"alphaBox": [
|
||||
321,
|
||||
314,
|
||||
419,
|
||||
354
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 41.11,
|
||||
"y": 29.69
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "lash-right",
|
||||
"source": "right lash group",
|
||||
"file": "lash-right.png",
|
||||
"alphaBox": [
|
||||
488,
|
||||
307,
|
||||
586,
|
||||
349
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 59.67,
|
||||
"y": 29.16
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "eyelid-upper-left",
|
||||
"source": "left upper eyelid group",
|
||||
"file": "eyelid-upper-left.png",
|
||||
"alphaBox": [
|
||||
293,
|
||||
327,
|
||||
415,
|
||||
378
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 39.33,
|
||||
"y": 31.33
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "eyelid-upper-right",
|
||||
"source": "right upper eyelid group",
|
||||
"file": "eyelid-upper-right.png",
|
||||
"alphaBox": [
|
||||
495,
|
||||
317,
|
||||
616,
|
||||
368
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 61.72,
|
||||
"y": 30.44
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "eyelid-lower-left",
|
||||
"source": "left lower eyelid group",
|
||||
"file": "eyelid-lower-left.png",
|
||||
"alphaBox": [
|
||||
320,
|
||||
368,
|
||||
412,
|
||||
389
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 40.67,
|
||||
"y": 33.64
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "eyelid-lower-right",
|
||||
"source": "right lower eyelid group",
|
||||
"file": "eyelid-lower-right.png",
|
||||
"alphaBox": [
|
||||
498,
|
||||
357,
|
||||
590,
|
||||
380
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 60.44,
|
||||
"y": 32.76
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "hair-front",
|
||||
"source": "front hair group",
|
||||
"file": "hair-front.png",
|
||||
"alphaBox": [
|
||||
212,
|
||||
20,
|
||||
692,
|
||||
829
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 50.22,
|
||||
"y": 37.73
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "hair-bangs",
|
||||
"source": "bangs group",
|
||||
"file": "hair-bangs.png",
|
||||
"alphaBox": [
|
||||
290,
|
||||
52,
|
||||
622,
|
||||
386
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 50.67,
|
||||
"y": 7.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "hair-side-left-1",
|
||||
"source": "left side hair 1 group",
|
||||
"file": "hair-side-left-1.png",
|
||||
"alphaBox": [
|
||||
212,
|
||||
21,
|
||||
457,
|
||||
441
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 37.17,
|
||||
"y": 6.35
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "hair-side-left-2",
|
||||
"source": "left side hair 2 group",
|
||||
"file": "hair-side-left-2.png",
|
||||
"alphaBox": [
|
||||
219,
|
||||
299,
|
||||
351,
|
||||
813
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 31.67,
|
||||
"y": 32.06
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "hair-side-right-1",
|
||||
"source": "right side hair 1 group",
|
||||
"file": "hair-side-right-1.png",
|
||||
"alphaBox": [
|
||||
447,
|
||||
20,
|
||||
692,
|
||||
440
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 63.28,
|
||||
"y": 6.26
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "hair-side-right-2",
|
||||
"source": "right side hair 2 group",
|
||||
"file": "hair-side-right-2.png",
|
||||
"alphaBox": [
|
||||
552,
|
||||
315,
|
||||
684,
|
||||
829
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 68.67,
|
||||
"y": 33.48
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "brow-left",
|
||||
"source": "hidden neutral left brow",
|
||||
"file": "brow-left.png",
|
||||
"alphaBox": [
|
||||
306,
|
||||
291,
|
||||
434,
|
||||
314
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 41.11,
|
||||
"y": 26.89
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "brow-right",
|
||||
"source": "hidden neutral right brow",
|
||||
"file": "brow-right.png",
|
||||
"alphaBox": [
|
||||
471,
|
||||
285,
|
||||
600,
|
||||
302
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 59.5,
|
||||
"y": 26.09
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "brow-sad-left",
|
||||
"source": "v2 crying left brow",
|
||||
"file": "brow-sad-left.png",
|
||||
"alphaBox": [
|
||||
290,
|
||||
281,
|
||||
430,
|
||||
311
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 40.0,
|
||||
"y": 26.31
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "brow-sad-right",
|
||||
"source": "v2 crying right brow",
|
||||
"file": "brow-sad-right.png",
|
||||
"alphaBox": [
|
||||
479,
|
||||
283,
|
||||
619,
|
||||
313
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 61.0,
|
||||
"y": 26.49
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "tear-left",
|
||||
"source": "v2 left tear layer",
|
||||
"file": "tear-left.png",
|
||||
"alphaBox": [
|
||||
320,
|
||||
369,
|
||||
416,
|
||||
390
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 40.89,
|
||||
"y": 33.73
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "tear-right",
|
||||
"source": "v2 right tear layer",
|
||||
"file": "tear-right.png",
|
||||
"alphaBox": [
|
||||
496,
|
||||
362,
|
||||
591,
|
||||
381
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 60.39,
|
||||
"y": 33.02
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "hair-back-base",
|
||||
"source": "top extra back hair layers",
|
||||
"file": "hair-back-base.png",
|
||||
"alphaBox": null,
|
||||
"originPercent": {
|
||||
"x": 50.0,
|
||||
"y": 50.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "mouth-neutral",
|
||||
"source": "hidden smile mouth group",
|
||||
"file": "mouth-neutral.png",
|
||||
"alphaBox": [
|
||||
426,
|
||||
454,
|
||||
493,
|
||||
474
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 51.06,
|
||||
"y": 41.24
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "mouth-sad",
|
||||
"source": "v2 crying mouth group",
|
||||
"file": "mouth-sad.png",
|
||||
"alphaBox": [
|
||||
424,
|
||||
456,
|
||||
490,
|
||||
474
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 50.78,
|
||||
"y": 41.33
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "mouth-open",
|
||||
"source": "hidden open mouth groups",
|
||||
"file": "mouth-open.png",
|
||||
"alphaBox": [
|
||||
400,
|
||||
424,
|
||||
519,
|
||||
539
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 51.06,
|
||||
"y": 42.8
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "mouth-warm",
|
||||
"source": "derived from neutral mouth bbox",
|
||||
"file": "mouth-warm.png",
|
||||
"alphaBox": [
|
||||
422,
|
||||
453,
|
||||
497,
|
||||
473
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 51.06,
|
||||
"y": 41.16
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "mouth-tired",
|
||||
"source": "derived from sad mouth bbox",
|
||||
"file": "mouth-tired.png",
|
||||
"alphaBox": [
|
||||
428,
|
||||
458,
|
||||
487,
|
||||
473
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 50.83,
|
||||
"y": 41.38
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "mouth-anxious",
|
||||
"source": "derived from sad mouth bbox",
|
||||
"file": "mouth-anxious.png",
|
||||
"alphaBox": [
|
||||
430,
|
||||
458,
|
||||
484,
|
||||
473
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 50.78,
|
||||
"y": 41.38
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "mouth-startled",
|
||||
"source": "derived from mouth-open bbox",
|
||||
"file": "mouth-startled.png",
|
||||
"alphaBox": [
|
||||
419,
|
||||
439,
|
||||
500,
|
||||
522
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 51.06,
|
||||
"y": 42.71
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "mouth-open-small",
|
||||
"source": "derived from mouth-open bbox",
|
||||
"file": "mouth-open-small.png",
|
||||
"alphaBox": [
|
||||
408,
|
||||
445,
|
||||
510,
|
||||
516
|
||||
],
|
||||
"originPercent": {
|
||||
"x": 51.0,
|
||||
"y": 42.71
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 694 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 8.9 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 7 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 80 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 269 KiB |
|
After Width: | Height: | Size: 266 KiB |
|
After Width: | Height: | Size: 122 KiB |
|
After Width: | Height: | Size: 284 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 61 KiB |
|
After Width: | Height: | Size: 5.4 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
|
After Width: | Height: | Size: 8 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 153 KiB |
|
After Width: | Height: | Size: 135 KiB |
|
After Width: | Height: | Size: 207 KiB |
|
After Width: | Height: | Size: 5 KiB |
|
After Width: | Height: | Size: 5 KiB |
|
After Width: | Height: | Size: 46 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 735 KiB |
|
After Width: | Height: | Size: 714 KiB |
|
After Width: | Height: | Size: 735 KiB |
|
After Width: | Height: | Size: 714 KiB |
|
After Width: | Height: | Size: 737 KiB |
|
After Width: | Height: | Size: 716 KiB |
304
docs/avatar-art/seoyeon/live2d-psd-v2/export_detailed_parts.py
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Export app-ready Live2D-style parts from the v2 PSD.
|
||||
|
||||
This reuses the PSB detailed-parts helpers but keeps the v2 PSD as a distinct
|
||||
art set so previous assets remain auditable.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from typing import Sequence
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image, ImageDraw
|
||||
from psd_tools import PSDImage
|
||||
from scipy import ndimage as ndi
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
REPO_ROOT = HERE.parents[3]
|
||||
BASE_SCRIPT = HERE.parent / "live2d-psb" / "export_detailed_parts.py"
|
||||
PSD = HERE / "source-latudi-female-v2.psd"
|
||||
OUT = HERE / "detailed-parts"
|
||||
PARTS = OUT / "parts"
|
||||
APP_PARTS = REPO_ROOT / "apps" / "web" / "public" / "avatar" / "seoyeon-live2d-psd-v2" / "parts"
|
||||
|
||||
|
||||
def load_base_module():
|
||||
spec = importlib.util.spec_from_file_location("seoyeon_psb_export", BASE_SCRIPT)
|
||||
if spec is None or spec.loader is None:
|
||||
raise RuntimeError(f"cannot load {BASE_SCRIPT}")
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
module.HERE = HERE
|
||||
module.REPO_ROOT = REPO_ROOT
|
||||
module.PSB = PSD
|
||||
module.OUT = OUT
|
||||
module.PARTS = PARTS
|
||||
module.APP_PARTS = APP_PARTS
|
||||
return module
|
||||
|
||||
|
||||
B = load_base_module()
|
||||
N = dict(B.N)
|
||||
N.update(
|
||||
{
|
||||
"cry_brow_left": "좌 울상 눈썹 ",
|
||||
"cry_brow_right": "우 울상 눈썹",
|
||||
"closed_eye_left": "좌 감은눈",
|
||||
"closed_eye_right": "우 감은눈",
|
||||
"tears": "눈물",
|
||||
"tear_left": "좌 눈물",
|
||||
"tear_right": "우 눈물",
|
||||
"cry_mouth": "우는 입",
|
||||
}
|
||||
)
|
||||
|
||||
NEUTRAL_LAYER_ORDER = [
|
||||
"hair-back-base",
|
||||
"hair-back-left",
|
||||
"hair-back-right",
|
||||
"body",
|
||||
"outfit",
|
||||
"ear-left",
|
||||
"ear-right",
|
||||
"face-base",
|
||||
"blush-left",
|
||||
"blush-right",
|
||||
"eye-white-left",
|
||||
"eye-white-right",
|
||||
"iris-left",
|
||||
"iris-right",
|
||||
"pupil-left",
|
||||
"pupil-right",
|
||||
"highlight-left",
|
||||
"highlight-right",
|
||||
"lash-left",
|
||||
"lash-right",
|
||||
"eyelid-upper-left",
|
||||
"eyelid-upper-right",
|
||||
"eyelid-lower-left",
|
||||
"eyelid-lower-right",
|
||||
"brow-left",
|
||||
"brow-right",
|
||||
"nose",
|
||||
"hair-side-left-1",
|
||||
"hair-side-right-1",
|
||||
"hair-side-left-2",
|
||||
"hair-side-right-2",
|
||||
"hair-bangs",
|
||||
"mouth-neutral",
|
||||
]
|
||||
|
||||
SAD_LAYER_ORDER = [
|
||||
*NEUTRAL_LAYER_ORDER[:24],
|
||||
"brow-sad-left",
|
||||
"brow-sad-right",
|
||||
"tear-left",
|
||||
"tear-right",
|
||||
"nose",
|
||||
"hair-side-left-1",
|
||||
"hair-side-right-1",
|
||||
"hair-side-left-2",
|
||||
"hair-side-right-2",
|
||||
"hair-bangs",
|
||||
"mouth-sad",
|
||||
]
|
||||
|
||||
|
||||
def visible(psd: PSDImage, root: Sequence[str], *, hidden: Sequence[Sequence[str]] = ()) -> Image.Image:
|
||||
return B.composite_with_visibility(psd, root, visible_paths=[root], hidden_paths=hidden)
|
||||
|
||||
|
||||
def enhance_tears(image: Image.Image) -> Image.Image:
|
||||
arr = np.array(image.convert("RGBA")).astype(np.float32)
|
||||
alpha = arr[:, :, 3]
|
||||
spread = ndi.maximum_filter(alpha, size=3)
|
||||
alpha = np.maximum(alpha, spread * 0.72)
|
||||
alpha = np.clip(alpha * 1.32, 0, 255)
|
||||
mask = alpha > 4
|
||||
cool = np.array([226, 241, 255], dtype=np.float32)
|
||||
arr[mask, :3] = arr[mask, :3] * 0.34 + cool * 0.66
|
||||
arr[:, :, 3] = alpha
|
||||
arr[arr[:, :, 3] < 1, :3] = 0
|
||||
return Image.fromarray(np.clip(arr, 0, 255).astype(np.uint8), "RGBA")
|
||||
|
||||
|
||||
def save_enhanced_part(entries: list[dict[str, object]], part_id: str, source: str, image: Image.Image) -> Image.Image:
|
||||
normalized = B.normalize(image) if image.size == B.SOURCE_CANVAS else image.convert("RGBA")
|
||||
normalized = enhance_tears(normalized)
|
||||
path = PARTS / f"{part_id}.png"
|
||||
normalized.save(path, optimize=True)
|
||||
box = B.alpha_bbox(normalized)
|
||||
entries.append(
|
||||
{
|
||||
"id": part_id,
|
||||
"source": source,
|
||||
"file": path.name,
|
||||
"alphaBox": list(box) if box else None,
|
||||
"originPercent": B.origin_for(part_id, box),
|
||||
}
|
||||
)
|
||||
return normalized
|
||||
|
||||
|
||||
def make_preview(name: str, order: Sequence[str]) -> None:
|
||||
preview = Image.new("RGBA", B.TARGET_CANVAS, (30, 39, 36, 255))
|
||||
transparent = B.blank(B.TARGET_CANVAS)
|
||||
for part_id in order:
|
||||
layer = Image.open(PARTS / f"{part_id}.png").convert("RGBA")
|
||||
preview.alpha_composite(layer)
|
||||
transparent.alpha_composite(layer)
|
||||
preview.save(OUT / f"preview-{name}.png", optimize=True)
|
||||
transparent.save(OUT / f"preview-{name}-transparent.png", optimize=True)
|
||||
if name == "neutral":
|
||||
preview.save(OUT / "preview-detailed-parts.png", optimize=True)
|
||||
transparent.save(OUT / "preview-detailed-parts-transparent.png", optimize=True)
|
||||
|
||||
|
||||
def sync_to_app() -> None:
|
||||
APP_PARTS.mkdir(parents=True, exist_ok=True)
|
||||
for source in PARTS.glob("*.png"):
|
||||
shutil.copy2(source, APP_PARTS / source.name)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if not PSD.exists():
|
||||
raise FileNotFoundError(PSD)
|
||||
|
||||
OUT.mkdir(parents=True, exist_ok=True)
|
||||
PARTS.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
psd = PSDImage.open(PSD)
|
||||
entries: list[dict[str, object]] = []
|
||||
|
||||
simple_parts: list[tuple[str, str, Sequence[str]]] = [
|
||||
("hair-back-left", "back hair left group", [N["back_hair"], N["back_hair_left"]]),
|
||||
("hair-back-right", "back hair right group", [N["back_hair"], N["back_hair_right"]]),
|
||||
("body", "body group", [N["body"]]),
|
||||
("neck", "neck group", [N["body"], N["neck"]]),
|
||||
("clavicle", "clavicle group", [N["body"], N["clavicle"]]),
|
||||
("outfit", "outfit group", [N["outfit"]]),
|
||||
("outfit-inner", "inner outfit group", [N["outfit"], N["inner_outfit"]]),
|
||||
("outfit-outer", "outer outfit group", [N["outfit"], N["outer_outfit"]]),
|
||||
("ribbon", "ribbon group", [N["outfit"], N["ribbon"]]),
|
||||
("face", "face group", [N["face"]]),
|
||||
("face-base", "face base group", [N["face"], N["face_base"]]),
|
||||
("ear-left", "left ear group", [N["face"], N["ear_left"]]),
|
||||
("ear-right", "right ear group", [N["face"], N["ear_right"]]),
|
||||
("blush-left", "left blush group", [N["face"], N["blush_left"]]),
|
||||
("blush-right", "right blush group", [N["face"], N["blush_right"]]),
|
||||
("nose", "nose group", [N["face"], N["nose"]]),
|
||||
("eye-left", "left eye group", [N["eye_left"]]),
|
||||
("eye-right", "right eye group", [N["eye_right"]]),
|
||||
("eye-white-left", "left eye white group", [N["eye_left"], N["white"]]),
|
||||
("eye-white-right", "right eye white group", [N["eye_right"], N["white_copy"]]),
|
||||
("iris-left", "left iris group", [N["eye_left"], N["iris"]]),
|
||||
("iris-right", "right iris group", [N["eye_right"], N["iris_copy"]]),
|
||||
("pupil-left", "left pupil group", [N["eye_left"], N["pupil"]]),
|
||||
("pupil-right", "right pupil group", [N["eye_right"], N["pupil_copy"]]),
|
||||
("highlight-left", "left eye highlight group", [N["eye_left"], N["highlight"]]),
|
||||
("highlight-right", "right eye highlight group", [N["eye_right"], N["highlight_copy"]]),
|
||||
("lash-left", "left lash group", [N["eye_left"], N["lash"]]),
|
||||
("lash-right", "right lash group", [N["eye_right"], N["lash_copy"]]),
|
||||
("eyelid-upper-left", "left upper eyelid group", [N["eye_left"], N["upper_lid"]]),
|
||||
("eyelid-upper-right", "right upper eyelid group", [N["eye_right"], N["upper_lid_copy"]]),
|
||||
("eyelid-lower-left", "left lower eyelid group", [N["eye_left"], N["lower_lid"]]),
|
||||
("eyelid-lower-right", "right lower eyelid group", [N["eye_right"], N["lower_lid_copy"]]),
|
||||
("hair-front", "front hair group", [N["front_hair"]]),
|
||||
("hair-bangs", "bangs group", [N["front_hair"], N["bangs"]]),
|
||||
("hair-side-left-1", "left side hair 1 group", [N["front_hair"], N["side_left_1"]]),
|
||||
("hair-side-left-2", "left side hair 2 group", [N["front_hair"], N["side_left_2"]]),
|
||||
("hair-side-right-1", "right side hair 1 group", [N["front_hair"], N["side_right_1"]]),
|
||||
("hair-side-right-2", "right side hair 2 group", [N["front_hair"], N["side_right_2"]]),
|
||||
]
|
||||
|
||||
for part_id, source, path in simple_parts:
|
||||
B.save_part(entries, part_id, source, B.composite_path(psd, path))
|
||||
|
||||
B.save_part(entries, "brow-left", "hidden neutral left brow", visible(psd, [N["eye_left"], N["brow"]]))
|
||||
B.save_part(entries, "brow-right", "hidden neutral right brow", visible(psd, [N["eye_right"], N["brow_copy"]]))
|
||||
B.save_part(entries, "brow-sad-left", "v2 crying left brow", visible(psd, [N["cry_brow_left"]]))
|
||||
B.save_part(entries, "brow-sad-right", "v2 crying right brow", visible(psd, [N["cry_brow_right"]]))
|
||||
save_enhanced_part(entries, "tear-left", "v2 left tear layer", visible(psd, [N["tears"], N["tear_left"]]))
|
||||
save_enhanced_part(entries, "tear-right", "v2 right tear layer", visible(psd, [N["tears"], N["tear_right"]]))
|
||||
|
||||
hair_back_base = B.composite_many(
|
||||
[
|
||||
B.composite_path(psd, [N["back_extra_left"]]),
|
||||
B.composite_path(psd, [N["back_extra_right"]]),
|
||||
]
|
||||
)
|
||||
B.save_part(entries, "hair-back-base", "top extra back hair layers", hair_back_base)
|
||||
|
||||
neutral_mouth = B.save_part(
|
||||
entries,
|
||||
"mouth-neutral",
|
||||
"hidden smile mouth group",
|
||||
visible(psd, [N["mouth"], N["smile"]], hidden=[[N["mouth"], N["cry_mouth"]]]),
|
||||
)
|
||||
sad_mouth = B.save_part(
|
||||
entries,
|
||||
"mouth-sad",
|
||||
"v2 crying mouth group",
|
||||
visible(psd, [N["mouth"], N["cry_mouth"]], hidden=[[N["mouth"], N["smile"]]]),
|
||||
)
|
||||
|
||||
mouth_open_groups = [
|
||||
[N["mouth"], N["mouth_inner"]],
|
||||
[N["mouth"], N["upper_teeth"]],
|
||||
[N["mouth"], N["lower_side_teeth"]],
|
||||
[N["mouth"], N["tongue"]],
|
||||
[N["mouth"], N["lower_teeth"]],
|
||||
[N["mouth"], N["upper_lip"]],
|
||||
[N["mouth"], N["lower_lip"]],
|
||||
]
|
||||
mouth_open = B.save_part(
|
||||
entries,
|
||||
"mouth-open",
|
||||
"hidden open mouth groups",
|
||||
B.composite_with_visibility(
|
||||
psd,
|
||||
[N["mouth"]],
|
||||
visible_paths=mouth_open_groups,
|
||||
hidden_paths=[[N["mouth"], N["smile"]], [N["mouth"], N["cry_mouth"]]],
|
||||
),
|
||||
)
|
||||
B.save_part(entries, "mouth-warm", "derived from neutral mouth bbox", B.paste_scaled(neutral_mouth, 1.12, 1.02, offset_y=-1))
|
||||
B.save_part(entries, "mouth-tired", "derived from sad mouth bbox", B.paste_scaled(sad_mouth, 0.9, 0.82, offset_y=0))
|
||||
B.save_part(entries, "mouth-anxious", "derived from sad mouth bbox", B.paste_scaled(sad_mouth, 0.82, 0.86, offset_y=0))
|
||||
B.save_part(entries, "mouth-startled", "derived from mouth-open bbox", B.paste_scaled(mouth_open, 0.68, 0.72, offset_y=-1))
|
||||
B.save_part(entries, "mouth-open-small", "derived from mouth-open bbox", B.paste_scaled(mouth_open, 0.86, 0.62, offset_y=-1))
|
||||
|
||||
make_preview("neutral", NEUTRAL_LAYER_ORDER)
|
||||
make_preview("sad", SAD_LAYER_ORDER)
|
||||
B.make_contact(entries)
|
||||
B.make_imagegen_harness(entries)
|
||||
|
||||
(OUT / "detailed-parts-manifest.json").write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"source": str(PSD),
|
||||
"sourceCanvas": B.SOURCE_CANVAS,
|
||||
"targetCanvas": B.TARGET_CANVAS,
|
||||
"cropBox": B.CROP_BOX,
|
||||
"neutralLayerOrder": NEUTRAL_LAYER_ORDER,
|
||||
"sadLayerOrder": SAD_LAYER_ORDER,
|
||||
"parts": entries,
|
||||
},
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
sync_to_app()
|
||||
print(f"exported {len(entries)} detailed parts")
|
||||
print(f"parts {PARTS}")
|
||||
print(f"app parts {APP_PARTS}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
After Width: | Height: | Size: 744 KiB |
BIN
docs/avatar-art/seoyeon/live2d-psd-v2/flattened-source.png
Normal file
|
After Width: | Height: | Size: 831 KiB |