Spaces:
Sleeping
Sleeping
ikeda
commited on
Commit
·
bac55b4
1
Parent(s):
ff60f42
add 3dmodelmaker sources
Browse files- .gitattributes +2 -0
- .gitignore +1 -0
- app.py +113 -0
- data/cards/back/cats.png +3 -0
- data/cards/back/fossils.png +3 -0
- data/cards/back/jewels.png +3 -0
- data/cards/front/cats.png +3 -0
- data/cards/front/fossils.png +3 -0
- data/cards/front/jewels.png +3 -0
- data/cards/史跡カード(背景).png +3 -0
- data/cards/史跡カード(裏面).png +3 -0
- data/cards/史跡カードフレーム(橙).png +3 -0
- data/cards/史跡カードフレーム(白).png +3 -0
- data/cards/史跡カードフレーム(紫).png +3 -0
- data/cards/史跡カードフレーム(緑).png +3 -0
- data/cards/史跡カードフレーム(茶).png +3 -0
- data/cards/史跡カードフレーム(赤).png +3 -0
- data/cards/史跡カードフレーム(青).png +3 -0
- data/cards/史跡カードフレーム(黄).png +3 -0
- data/cards/史跡カードフレーム(黒).png +3 -0
- data/cards/史跡カード指定マーク(区指定).png +3 -0
- data/cards/史跡カード指定マーク(国指定).png +3 -0
- data/cards/史跡カード指定マーク(市指定).png +3 -0
- data/cards/史跡カード指定マーク(府指定).png +3 -0
- data/cards/史跡カード指定マーク(村指定).png +3 -0
- data/cards/史跡カード指定マーク(町指定).png +3 -0
- data/cards/史跡カード指定マーク(県指定).png +3 -0
- data/cards/史跡カード指定マーク(道指定).png +3 -0
- data/cards/史跡カード指定マーク(都指定).png +3 -0
- data/fonts/SourceHanSans-Bold.otf +3 -0
- data/fonts/SourceHanSans_LICENSE.txt +96 -0
- data/fonts/SourceHanSerif-Bold.otf +3 -0
- data/fonts/SourceHanSerif_LICENSE.txt +96 -0
- license.md +24 -0
- readme.txt +2 -0
- requirements.txt +6 -0
- src/card_model.py +311 -0
- src/constants.py +80 -0
- src/display_message_en.json +37 -0
- src/display_message_ja.json +37 -0
- src/extracted_objects_model.py +265 -0
- src/front_card_image.py +91 -0
- src/front_card_image_historic_site.py +150 -0
- src/picture_box_model.py +265 -0
.gitattributes
CHANGED
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
36 |
+
*.png filter=lfs diff=lfs merge=lfs -text
|
37 |
+
*.otf filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
*.pyc
|
app.py
ADDED
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
from PIL import Image as PIL_Image # gltflibのImageと被るので別名にする。
|
3 |
+
from io import BytesIO
|
4 |
+
import json
|
5 |
+
import os
|
6 |
+
|
7 |
+
import src.constants as constants
|
8 |
+
|
9 |
+
from src.front_card_image import create_card_image
|
10 |
+
from src.front_card_image_historic_site import create_historic_site_card_image
|
11 |
+
from src.picture_box_model import create_picture_box_model
|
12 |
+
from src.extracted_objects_model import create_extracted_objects_model
|
13 |
+
from src.card_model import create_card_model
|
14 |
+
|
15 |
+
language = os.environ.get('LANGUAGE', 'en')
|
16 |
+
print(f'LANGUAGE: {language}')
|
17 |
+
|
18 |
+
with open(f'src/display_message_{language}.json', 'r', encoding='utf-8') as f:
|
19 |
+
display_message_dict = json.load(f)
|
20 |
+
model_type_dict = display_message_dict['model_type_dict']
|
21 |
+
color_dict = display_message_dict['color_dict']
|
22 |
+
|
23 |
+
def create_3dmodel(model_no, title, color, mark, historic_site_type, difficulty, description, is_thick, image):
|
24 |
+
|
25 |
+
img_bytearray = BytesIO()
|
26 |
+
image['background'].save(img_bytearray, "JPEG", quality=95)
|
27 |
+
img_bytearray.seek(0) # 画像の先頭にシークしないと空データになってしまう。
|
28 |
+
|
29 |
+
option_dict = {
|
30 |
+
'タイトル': title,
|
31 |
+
'色': color,
|
32 |
+
'マーク': mark,
|
33 |
+
'史跡種類': historic_site_type,
|
34 |
+
'訪問難度': difficulty,
|
35 |
+
'説明文': description,
|
36 |
+
'厚み': '有' if is_thick else '無',
|
37 |
+
}
|
38 |
+
|
39 |
+
# model_noのコード値決定ルールを接頭語を付けた場合分けやファクトリ―クラスの作成を検討した方が良い
|
40 |
+
if model_no not in ['A', 'B']:
|
41 |
+
# カード画像(表面)の作成
|
42 |
+
if model_no == '1':
|
43 |
+
front_img_bytearray = create_historic_site_card_image(img_bytearray, option_dict)
|
44 |
+
else:
|
45 |
+
front_img_bytearray = create_card_image(model_no, img_bytearray, option_dict)
|
46 |
+
|
47 |
+
# カード画像(裏面)の取得
|
48 |
+
back_path = constants.back_card_img_dict[model_no]
|
49 |
+
back_img = PIL_Image.open(back_path)
|
50 |
+
back_img_bytearray = BytesIO()
|
51 |
+
back_img.convert('RGB').save(back_img_bytearray, "JPEG", quality=95)
|
52 |
+
back_img_bytearray.seek(0) # 画像の先頭にシークしないと空データになってしまう。
|
53 |
+
|
54 |
+
# カードの3Dモデルの作成(戻り値は作成したモデルのパス)
|
55 |
+
model_path = create_card_model(front_img_bytearray, back_img_bytearray, option_dict)
|
56 |
+
|
57 |
+
else:
|
58 |
+
# 3Dモデルの作成(戻り値は作成したモデルのパス)
|
59 |
+
if model_no == 'A':
|
60 |
+
model_path = create_picture_box_model(img_bytearray)
|
61 |
+
if model_no == 'B':
|
62 |
+
model_path = create_extracted_objects_model(img_bytearray)
|
63 |
+
|
64 |
+
# 作成した3Dモデルの送信
|
65 |
+
return model_path
|
66 |
+
|
67 |
+
with gr.Blocks() as demo:
|
68 |
+
|
69 |
+
gr.Markdown(display_message_dict['header'])
|
70 |
+
|
71 |
+
with gr.Tab(display_message_dict['tab_label_card_general']):
|
72 |
+
with gr.Row():
|
73 |
+
with gr.Column():
|
74 |
+
title = gr.Textbox(label=display_message_dict['label_title'], placeholder=display_message_dict['placeholder_title'])
|
75 |
+
model_type = gr.Radio([(model_type_dict[key], key) for key in model_type_dict], value='2', label=display_message_dict['label_card_type'])
|
76 |
+
with gr.Column():
|
77 |
+
description = gr.Textbox(lines=2, label=display_message_dict['label_description'], placeholder=display_message_dict['placeholder_description'])
|
78 |
+
is_thick = gr.Checkbox(label=display_message_dict['label_is_thick'], value=False, info=display_message_dict['info_is_thick'])
|
79 |
+
image = gr.ImageEditor(image_mode='RGB', sources="upload", type="pil", crop_size="1:1", label=display_message_dict['label_image'])
|
80 |
+
|
81 |
+
button = gr.Button(display_message_dict['label_button'])
|
82 |
+
button.click(
|
83 |
+
fn=lambda model_type, title, description, is_thick, image:
|
84 |
+
create_3dmodel(model_type, title, None, None, None, None, description, is_thick, image),
|
85 |
+
inputs=[model_type, title, description, is_thick, image],
|
86 |
+
outputs=[gr.Model3D(camera_position=(90, 90, 5))]
|
87 |
+
)
|
88 |
+
|
89 |
+
if language == 'ja':
|
90 |
+
with gr.Tab(display_message_dict['tab_label_historic_site_card']):
|
91 |
+
with gr.Row():
|
92 |
+
with gr.Column():
|
93 |
+
title = gr.Textbox(label=display_message_dict['label_title'], placeholder=display_message_dict['placeholder_title'])
|
94 |
+
color = gr.Radio([(color_dict[key], key) for key in color_dict], value=list(color_dict)[0], label=display_message_dict['label_color'])
|
95 |
+
mark = gr.Radio(display_message_dict['mark_list'], value=list(display_message_dict['mark_list'])[0], label=display_message_dict['label_mark'])
|
96 |
+
historic_site_type = gr.Textbox(label=display_message_dict["label_historic_site_type"], placeholder=display_message_dict["placeholder_historic_site_type"])
|
97 |
+
with gr.Column():
|
98 |
+
difficulty = gr.Slider(1, 5, 3, step=1, label=display_message_dict['label_difficulty'])
|
99 |
+
description = gr.Textbox(lines=2, label=display_message_dict['label_description'], placeholder=display_message_dict['placeholder_description'])
|
100 |
+
is_thick = gr.Checkbox(label=display_message_dict['label_is_thick'], value=False, info=display_message_dict['info_is_thick'])
|
101 |
+
image = gr.ImageEditor(image_mode='RGB', sources="upload", type="pil", crop_size="1:1", label=display_message_dict['label_image'])
|
102 |
+
|
103 |
+
button = gr.Button(display_message_dict['label_button'])
|
104 |
+
button.click(
|
105 |
+
fn=lambda title, color, mark, historic_site_type, difficulty, description, is_thick, image:
|
106 |
+
create_3dmodel('1', title, color, mark, historic_site_type, difficulty, description, is_thick, image),
|
107 |
+
inputs=[title, color, mark, historic_site_type, difficulty, description, is_thick, image],
|
108 |
+
outputs=[gr.Model3D(camera_position=(90, 90, 5))]
|
109 |
+
)
|
110 |
+
|
111 |
+
gr.Markdown(display_message_dict['footer_historic_site_card'])
|
112 |
+
|
113 |
+
demo.launch()
|
data/cards/back/cats.png
ADDED
![]() |
Git LFS Details
|
data/cards/back/fossils.png
ADDED
![]() |
Git LFS Details
|
data/cards/back/jewels.png
ADDED
![]() |
Git LFS Details
|
data/cards/front/cats.png
ADDED
![]() |
Git LFS Details
|
data/cards/front/fossils.png
ADDED
![]() |
Git LFS Details
|
data/cards/front/jewels.png
ADDED
![]() |
Git LFS Details
|
data/cards/史跡カード(背景).png
ADDED
![]() |
Git LFS Details
|
data/cards/史跡カード(裏面).png
ADDED
![]() |
Git LFS Details
|
data/cards/史跡カードフレーム(橙).png
ADDED
![]() |
Git LFS Details
|
data/cards/史跡カードフレーム(白).png
ADDED
![]() |
Git LFS Details
|
data/cards/史跡カードフレーム(紫).png
ADDED
![]() |
Git LFS Details
|
data/cards/史跡カードフレーム(緑).png
ADDED
![]() |
Git LFS Details
|
data/cards/史跡カードフレーム(茶).png
ADDED
![]() |
Git LFS Details
|
data/cards/史跡カードフレーム(赤).png
ADDED
![]() |
Git LFS Details
|
data/cards/史跡カードフレーム(青).png
ADDED
![]() |
Git LFS Details
|
data/cards/史跡カードフレーム(黄).png
ADDED
![]() |
Git LFS Details
|
data/cards/史跡カードフレーム(黒).png
ADDED
![]() |
Git LFS Details
|
data/cards/史跡カード指定マーク(区指定).png
ADDED
![]() |
Git LFS Details
|
data/cards/史跡カード指定マーク(国指定).png
ADDED
![]() |
Git LFS Details
|
data/cards/史跡カード指定マーク(市指定).png
ADDED
![]() |
Git LFS Details
|
data/cards/史跡カード指定マーク(府指定).png
ADDED
![]() |
Git LFS Details
|
data/cards/史跡カード指定マーク(村指定).png
ADDED
![]() |
Git LFS Details
|
data/cards/史跡カード指定マーク(町指定).png
ADDED
![]() |
Git LFS Details
|
data/cards/史跡カード指定マーク(県指定).png
ADDED
![]() |
Git LFS Details
|
data/cards/史跡カード指定マーク(道指定).png
ADDED
![]() |
Git LFS Details
|
data/cards/史跡カード指定マーク(都指定).png
ADDED
![]() |
Git LFS Details
|
data/fonts/SourceHanSans-Bold.otf
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:a6e567d2ac4458caa26ab1bbbcad9de587d6be7f798f761e66a0c237383f19bb
|
3 |
+
size 17032800
|
data/fonts/SourceHanSans_LICENSE.txt
ADDED
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Copyright 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font
|
2 |
+
Name 'Source'. Source is a trademark of Adobe in the United States
|
3 |
+
and/or other countries.
|
4 |
+
|
5 |
+
This Font Software is licensed under the SIL Open Font License,
|
6 |
+
Version 1.1.
|
7 |
+
|
8 |
+
This license is copied below, and is also available with a FAQ at:
|
9 |
+
http://scripts.sil.org/OFL
|
10 |
+
|
11 |
+
-----------------------------------------------------------
|
12 |
+
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
13 |
+
-----------------------------------------------------------
|
14 |
+
|
15 |
+
PREAMBLE
|
16 |
+
The goals of the Open Font License (OFL) are to stimulate worldwide
|
17 |
+
development of collaborative font projects, to support the font
|
18 |
+
creation efforts of academic and linguistic communities, and to
|
19 |
+
provide a free and open framework in which fonts may be shared and
|
20 |
+
improved in partnership with others.
|
21 |
+
|
22 |
+
The OFL allows the licensed fonts to be used, studied, modified and
|
23 |
+
redistributed freely as long as they are not sold by themselves. The
|
24 |
+
fonts, including any derivative works, can be bundled, embedded,
|
25 |
+
redistributed and/or sold with any software provided that any reserved
|
26 |
+
names are not used by derivative works. The fonts and derivatives,
|
27 |
+
however, cannot be released under any other type of license. The
|
28 |
+
requirement for fonts to remain under this license does not apply to
|
29 |
+
any document created using the fonts or their derivatives.
|
30 |
+
|
31 |
+
DEFINITIONS
|
32 |
+
"Font Software" refers to the set of files released by the Copyright
|
33 |
+
Holder(s) under this license and clearly marked as such. This may
|
34 |
+
include source files, build scripts and documentation.
|
35 |
+
|
36 |
+
"Reserved Font Name" refers to any names specified as such after the
|
37 |
+
copyright statement(s).
|
38 |
+
|
39 |
+
"Original Version" refers to the collection of Font Software
|
40 |
+
components as distributed by the Copyright Holder(s).
|
41 |
+
|
42 |
+
"Modified Version" refers to any derivative made by adding to,
|
43 |
+
deleting, or substituting -- in part or in whole -- any of the
|
44 |
+
components of the Original Version, by changing formats or by porting
|
45 |
+
the Font Software to a new environment.
|
46 |
+
|
47 |
+
"Author" refers to any designer, engineer, programmer, technical
|
48 |
+
writer or other person who contributed to the Font Software.
|
49 |
+
|
50 |
+
PERMISSION & CONDITIONS
|
51 |
+
Permission is hereby granted, free of charge, to any person obtaining
|
52 |
+
a copy of the Font Software, to use, study, copy, merge, embed,
|
53 |
+
modify, redistribute, and sell modified and unmodified copies of the
|
54 |
+
Font Software, subject to the following conditions:
|
55 |
+
|
56 |
+
1) Neither the Font Software nor any of its individual components, in
|
57 |
+
Original or Modified Versions, may be sold by itself.
|
58 |
+
|
59 |
+
2) Original or Modified Versions of the Font Software may be bundled,
|
60 |
+
redistributed and/or sold with any software, provided that each copy
|
61 |
+
contains the above copyright notice and this license. These can be
|
62 |
+
included either as stand-alone text files, human-readable headers or
|
63 |
+
in the appropriate machine-readable metadata fields within text or
|
64 |
+
binary files as long as those fields can be easily viewed by the user.
|
65 |
+
|
66 |
+
3) No Modified Version of the Font Software may use the Reserved Font
|
67 |
+
Name(s) unless explicit written permission is granted by the
|
68 |
+
corresponding Copyright Holder. This restriction only applies to the
|
69 |
+
primary font name as presented to the users.
|
70 |
+
|
71 |
+
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
72 |
+
Software shall not be used to promote, endorse or advertise any
|
73 |
+
Modified Version, except to acknowledge the contribution(s) of the
|
74 |
+
Copyright Holder(s) and the Author(s) or with their explicit written
|
75 |
+
permission.
|
76 |
+
|
77 |
+
5) The Font Software, modified or unmodified, in part or in whole,
|
78 |
+
must be distributed entirely under this license, and must not be
|
79 |
+
distributed under any other license. The requirement for fonts to
|
80 |
+
remain under this license does not apply to any document created using
|
81 |
+
the Font Software.
|
82 |
+
|
83 |
+
TERMINATION
|
84 |
+
This license becomes null and void if any of the above conditions are
|
85 |
+
not met.
|
86 |
+
|
87 |
+
DISCLAIMER
|
88 |
+
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
89 |
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
90 |
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
91 |
+
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
92 |
+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
93 |
+
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
94 |
+
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
95 |
+
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
96 |
+
OTHER DEALINGS IN THE FONT SOFTWARE.
|
data/fonts/SourceHanSerif-Bold.otf
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
version https://git-lfs.github.com/spec/v1
|
2 |
+
oid sha256:7100a6ab4f5847220347d915d784100eeaa0a8f145b666f2bfbe1565dc6b9610
|
3 |
+
size 25455872
|
data/fonts/SourceHanSerif_LICENSE.txt
ADDED
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Copyright 2017-2022 Adobe (http://www.adobe.com/), with Reserved Font
|
2 |
+
Name 'Source'. Source is a trademark of Adobe in the United States
|
3 |
+
and/or other countries.
|
4 |
+
|
5 |
+
This Font Software is licensed under the SIL Open Font License,
|
6 |
+
Version 1.1.
|
7 |
+
|
8 |
+
This license is copied below, and is also available with a FAQ at:
|
9 |
+
http://scripts.sil.org/OFL
|
10 |
+
|
11 |
+
-----------------------------------------------------------
|
12 |
+
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
13 |
+
-----------------------------------------------------------
|
14 |
+
|
15 |
+
PREAMBLE
|
16 |
+
The goals of the Open Font License (OFL) are to stimulate worldwide
|
17 |
+
development of collaborative font projects, to support the font
|
18 |
+
creation efforts of academic and linguistic communities, and to
|
19 |
+
provide a free and open framework in which fonts may be shared and
|
20 |
+
improved in partnership with others.
|
21 |
+
|
22 |
+
The OFL allows the licensed fonts to be used, studied, modified and
|
23 |
+
redistributed freely as long as they are not sold by themselves. The
|
24 |
+
fonts, including any derivative works, can be bundled, embedded,
|
25 |
+
redistributed and/or sold with any software provided that any reserved
|
26 |
+
names are not used by derivative works. The fonts and derivatives,
|
27 |
+
however, cannot be released under any other type of license. The
|
28 |
+
requirement for fonts to remain under this license does not apply to
|
29 |
+
any document created using the fonts or their derivatives.
|
30 |
+
|
31 |
+
DEFINITIONS
|
32 |
+
"Font Software" refers to the set of files released by the Copyright
|
33 |
+
Holder(s) under this license and clearly marked as such. This may
|
34 |
+
include source files, build scripts and documentation.
|
35 |
+
|
36 |
+
"Reserved Font Name" refers to any names specified as such after the
|
37 |
+
copyright statement(s).
|
38 |
+
|
39 |
+
"Original Version" refers to the collection of Font Software
|
40 |
+
components as distributed by the Copyright Holder(s).
|
41 |
+
|
42 |
+
"Modified Version" refers to any derivative made by adding to,
|
43 |
+
deleting, or substituting -- in part or in whole -- any of the
|
44 |
+
components of the Original Version, by changing formats or by porting
|
45 |
+
the Font Software to a new environment.
|
46 |
+
|
47 |
+
"Author" refers to any designer, engineer, programmer, technical
|
48 |
+
writer or other person who contributed to the Font Software.
|
49 |
+
|
50 |
+
PERMISSION & CONDITIONS
|
51 |
+
Permission is hereby granted, free of charge, to any person obtaining
|
52 |
+
a copy of the Font Software, to use, study, copy, merge, embed,
|
53 |
+
modify, redistribute, and sell modified and unmodified copies of the
|
54 |
+
Font Software, subject to the following conditions:
|
55 |
+
|
56 |
+
1) Neither the Font Software nor any of its individual components, in
|
57 |
+
Original or Modified Versions, may be sold by itself.
|
58 |
+
|
59 |
+
2) Original or Modified Versions of the Font Software may be bundled,
|
60 |
+
redistributed and/or sold with any software, provided that each copy
|
61 |
+
contains the above copyright notice and this license. These can be
|
62 |
+
included either as stand-alone text files, human-readable headers or
|
63 |
+
in the appropriate machine-readable metadata fields within text or
|
64 |
+
binary files as long as those fields can be easily viewed by the user.
|
65 |
+
|
66 |
+
3) No Modified Version of the Font Software may use the Reserved Font
|
67 |
+
Name(s) unless explicit written permission is granted by the
|
68 |
+
corresponding Copyright Holder. This restriction only applies to the
|
69 |
+
primary font name as presented to the users.
|
70 |
+
|
71 |
+
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
72 |
+
Software shall not be used to promote, endorse or advertise any
|
73 |
+
Modified Version, except to acknowledge the contribution(s) of the
|
74 |
+
Copyright Holder(s) and the Author(s) or with their explicit written
|
75 |
+
permission.
|
76 |
+
|
77 |
+
5) The Font Software, modified or unmodified, in part or in whole,
|
78 |
+
must be distributed entirely under this license, and must not be
|
79 |
+
distributed under any other license. The requirement for fonts to
|
80 |
+
remain under this license does not apply to any document created using
|
81 |
+
the Font Software.
|
82 |
+
|
83 |
+
TERMINATION
|
84 |
+
This license becomes null and void if any of the above conditions are
|
85 |
+
not met.
|
86 |
+
|
87 |
+
DISCLAIMER
|
88 |
+
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
89 |
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
90 |
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
91 |
+
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
92 |
+
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
93 |
+
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
94 |
+
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
95 |
+
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
96 |
+
OTHER DEALINGS IN THE FONT SOFTWARE.
|
license.md
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
MIT License
|
2 |
+
|
3 |
+
Copyright (c) 2023 Pieno Inc.
|
4 |
+
|
5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6 |
+
of this software and associated documentation files
|
7 |
+
(/app.py, /src/*, /requirements.txt, /readme.md and /data/cards/*, the "Software"), to deal
|
8 |
+
in the Software without restriction, including without limitation the rights
|
9 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10 |
+
copies of the Software, and to permit persons to whom the Software is
|
11 |
+
furnished to do so, subject to the following conditions:
|
12 |
+
|
13 |
+
The above copyright notice and this permission notice shall be included in all
|
14 |
+
copies or substantial portions of the Software.
|
15 |
+
|
16 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
22 |
+
SOFTWARE.
|
23 |
+
|
24 |
+
|
readme.txt
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
This software(/app.py, /src/*, /requirements.txt, /readme.md and /data/cards/*) is released under the MIT License, see LICENSE.md.
|
2 |
+
/data/fonts/SourceHanSans-Bold.otf and SourceHanSerif-Bold.otf are released under the SIL OPEN FONT LICENSE, see SourceHanSans_LICENSE.txt and SourceHanSerif_LICENSE.txt.
|
requirements.txt
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
gradio==4.8.0
|
2 |
+
Pillow==9.5.0
|
3 |
+
numpy==1.23.5
|
4 |
+
gltflib==1.0.13
|
5 |
+
triangle==20230923
|
6 |
+
opencv-python==4.8.0.76
|
src/card_model.py
ADDED
@@ -0,0 +1,311 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import io
|
2 |
+
import numpy as np
|
3 |
+
from PIL import Image as PIL_Image # gltflibのImageと被るので別名にする。
|
4 |
+
import struct
|
5 |
+
import uuid
|
6 |
+
|
7 |
+
from gltflib import (
|
8 |
+
GLTF, GLTFModel, Asset, Scene, Node, Mesh, Primitive, Attributes, Buffer, BufferView, Image, Texture, TextureInfo, Material, Sampler, Accessor, AccessorType,
|
9 |
+
BufferTarget, ComponentType, GLBResource, PBRMetallicRoughness)
|
10 |
+
|
11 |
+
# 共通設定情報
|
12 |
+
# バイナリ部分のオフセットやサイズに関わらない部分は予め定義できる。
|
13 |
+
# アセット
|
14 |
+
asset=Asset()
|
15 |
+
|
16 |
+
# イメージ
|
17 |
+
images=[
|
18 |
+
Image(mimeType='image/jpeg', bufferView=4),
|
19 |
+
Image(mimeType='image/jpeg',bufferView=5),
|
20 |
+
Image(mimeType='image/jpeg',bufferView=6),
|
21 |
+
Image(mimeType='image/jpeg',bufferView=7),
|
22 |
+
Image(mimeType='image/jpeg',bufferView=8),
|
23 |
+
Image(mimeType='image/jpeg',bufferView=9),
|
24 |
+
]
|
25 |
+
# サンプラー
|
26 |
+
samplers = [Sampler(magFilter=9728, minFilter=9984)] # magFilter:最近傍フィルタリング、minFilter:ミップマップ+最近傍フィルタリング
|
27 |
+
|
28 |
+
# テクスチャ
|
29 |
+
textures = [
|
30 |
+
Texture(name='Front',sampler=0,source=0),
|
31 |
+
Texture(name='Back',sampler=0,source=1),
|
32 |
+
Texture(name='Left',sampler=0,source=2), # 左
|
33 |
+
Texture(name='Right',sampler=0,source=3), # 右
|
34 |
+
Texture(name='Top',sampler=0,source=4), # 上
|
35 |
+
Texture(name='Bottom',sampler=0,source=5), # 下
|
36 |
+
]
|
37 |
+
|
38 |
+
# マテリアル
|
39 |
+
materials = [
|
40 |
+
Material(
|
41 |
+
pbrMetallicRoughness=PBRMetallicRoughness(
|
42 |
+
baseColorTexture=TextureInfo(index=0),
|
43 |
+
metallicFactor=0,
|
44 |
+
roughnessFactor=0.5
|
45 |
+
),
|
46 |
+
name='Material0',
|
47 |
+
alphaMode='OPAQUE',
|
48 |
+
doubleSided=False
|
49 |
+
),
|
50 |
+
Material(
|
51 |
+
pbrMetallicRoughness=PBRMetallicRoughness(
|
52 |
+
baseColorTexture=TextureInfo(index=1),
|
53 |
+
metallicFactor=0,
|
54 |
+
roughnessFactor=0.5
|
55 |
+
),
|
56 |
+
name='Material1',
|
57 |
+
alphaMode='OPAQUE',
|
58 |
+
doubleSided=False
|
59 |
+
),
|
60 |
+
Material(
|
61 |
+
pbrMetallicRoughness=PBRMetallicRoughness(
|
62 |
+
baseColorTexture=TextureInfo(index=2),
|
63 |
+
metallicFactor=0,
|
64 |
+
roughnessFactor=0.5
|
65 |
+
),
|
66 |
+
name='Material2',
|
67 |
+
alphaMode='OPAQUE',
|
68 |
+
doubleSided=True
|
69 |
+
),
|
70 |
+
Material(
|
71 |
+
pbrMetallicRoughness=PBRMetallicRoughness(
|
72 |
+
baseColorTexture=TextureInfo(index=3),
|
73 |
+
metallicFactor=0,
|
74 |
+
roughnessFactor=0.5
|
75 |
+
),
|
76 |
+
name='Material3',
|
77 |
+
alphaMode='OPAQUE',
|
78 |
+
doubleSided=True
|
79 |
+
),
|
80 |
+
Material(
|
81 |
+
pbrMetallicRoughness=PBRMetallicRoughness(
|
82 |
+
baseColorTexture=TextureInfo(index=4),
|
83 |
+
metallicFactor=0,
|
84 |
+
roughnessFactor=0.5
|
85 |
+
),
|
86 |
+
name='Material4',
|
87 |
+
alphaMode='OPAQUE',
|
88 |
+
doubleSided=True
|
89 |
+
),
|
90 |
+
Material(
|
91 |
+
pbrMetallicRoughness=PBRMetallicRoughness(
|
92 |
+
baseColorTexture=TextureInfo(index=5),
|
93 |
+
metallicFactor=0,
|
94 |
+
roughnessFactor=0.5
|
95 |
+
),
|
96 |
+
name='Material5',
|
97 |
+
alphaMode='OPAQUE',
|
98 |
+
doubleSided=True
|
99 |
+
),
|
100 |
+
]
|
101 |
+
|
102 |
+
# メッシュ
|
103 |
+
meshes = [
|
104 |
+
Mesh(name='Front', primitives=[Primitive(attributes=Attributes(POSITION=0, NORMAL=1,TEXCOORD_0=2), indices=3, material=0)]),
|
105 |
+
Mesh(name='Back', primitives=[Primitive(attributes=Attributes(POSITION=0, NORMAL=1,TEXCOORD_0=2), indices=3, material=1)]),
|
106 |
+
Mesh(name='Left', primitives=[Primitive(attributes=Attributes(POSITION=0, NORMAL=1,TEXCOORD_0=2), indices=3, material=2)]),
|
107 |
+
Mesh(name='Right', primitives=[Primitive(attributes=Attributes(POSITION=0, NORMAL=1,TEXCOORD_0=2), indices=3, material=3)]),
|
108 |
+
Mesh(name='Top', primitives=[Primitive(attributes=Attributes(POSITION=0, NORMAL=1,TEXCOORD_0=2), indices=3, material=4)]),
|
109 |
+
Mesh(name='Bottom', primitives=[Primitive(attributes=Attributes(POSITION=0, NORMAL=1,TEXCOORD_0=2), indices=3, material=5)]),
|
110 |
+
]
|
111 |
+
|
112 |
+
def create_card_model(front_img_bytearray, back_img_bytearray, option_dict):
|
113 |
+
|
114 |
+
is_thick = True if option_dict['厚み'] == '有' else False
|
115 |
+
|
116 |
+
# 表面画像
|
117 |
+
front_img = PIL_Image.open(front_img_bytearray).convert('RGB')
|
118 |
+
front_bytearray = io.BytesIO()
|
119 |
+
front_img.save(front_bytearray, format="JPEG", quality=95) # JPEG保存を強制
|
120 |
+
front_bytearray = front_bytearray.getvalue()
|
121 |
+
front_bytelen = len(front_bytearray)
|
122 |
+
|
123 |
+
# 裏面画像
|
124 |
+
back_img = PIL_Image.open(back_img_bytearray).convert('RGB')
|
125 |
+
back_bytearray = io.BytesIO()
|
126 |
+
back_img.save(back_bytearray, format="JPEG", quality=95) # JPEG保存を強制
|
127 |
+
back_bytearray = back_bytearray.getvalue()
|
128 |
+
back_bytelen = len(back_bytearray)
|
129 |
+
|
130 |
+
# 側面画像の作成(裏面の端の色を延長する)
|
131 |
+
back_img_array = np.array(back_img)
|
132 |
+
left_side_img = PIL_Image.fromarray(np.tile(back_img_array[:, [0], :], [1, 32, 1]))
|
133 |
+
right_side_img = PIL_Image.fromarray(np.tile(back_img_array[:, [back_img_array.shape[1] - 1], :], [1, 32, 1]))
|
134 |
+
top_side_img = PIL_Image.fromarray(np.tile(back_img_array[[0], :, :], [32, 1, 1]))
|
135 |
+
bottom_side_img = PIL_Image.fromarray(np.tile(back_img_array[[back_img_array.shape[0] - 1], :, :], [32, 1, 1]))
|
136 |
+
left_side_bytearray = io.BytesIO()
|
137 |
+
left_side_img.save(left_side_bytearray, format="JPEG", quality=95) # JPEG保存を強制
|
138 |
+
left_side_bytearray = left_side_bytearray.getvalue()
|
139 |
+
left_side_bytelen = len(left_side_bytearray)
|
140 |
+
right_side_bytearray = io.BytesIO()
|
141 |
+
right_side_img.save(right_side_bytearray, format="JPEG", quality=95) # JPEG保存を強制
|
142 |
+
right_side_bytearray = right_side_bytearray.getvalue()
|
143 |
+
right_side_bytelen = len(right_side_bytearray)
|
144 |
+
top_side_bytearray = io.BytesIO()
|
145 |
+
top_side_img.save(top_side_bytearray, format="JPEG", quality=95) # JPEG保存を強制
|
146 |
+
top_side_bytearray = top_side_bytearray.getvalue()
|
147 |
+
top_side_bytelen = len(top_side_bytearray)
|
148 |
+
bottom_side_bytearray = io.BytesIO()
|
149 |
+
bottom_side_img.save(bottom_side_bytearray, format="JPEG", quality=95) # JPEG保存を強制
|
150 |
+
bottom_side_bytearray = bottom_side_bytearray.getvalue()
|
151 |
+
bottom_side_bytelen = len(bottom_side_bytearray)
|
152 |
+
|
153 |
+
# 頂点データ(POSITION)
|
154 |
+
vertices = [
|
155 |
+
(-1.0, -1.0, 0.0),
|
156 |
+
( 1.0, -1.0, 0.0),
|
157 |
+
(-1.0, 1.0, 0.0),
|
158 |
+
( 1.0, 1.0, 0.0)
|
159 |
+
]
|
160 |
+
vertex_bytearray = bytearray()
|
161 |
+
for vertex in vertices:
|
162 |
+
for value in vertex:
|
163 |
+
vertex_bytearray.extend(struct.pack('f', value))
|
164 |
+
vertex_bytelen = len(vertex_bytearray)
|
165 |
+
mins = [min([vertex[i] for vertex in vertices]) for i in range(3)]
|
166 |
+
maxs = [max([vertex[i] for vertex in vertices]) for i in range(3)]
|
167 |
+
|
168 |
+
# 法線データ(NORMAL)
|
169 |
+
normals = [( 0.0, 0.0, 1.0)] * 4
|
170 |
+
normal_bytearray = bytearray()
|
171 |
+
for normal in normals:
|
172 |
+
for value in normal:
|
173 |
+
normal_bytearray.extend(struct.pack('f', value))
|
174 |
+
normal_bytelen = len(normal_bytearray)
|
175 |
+
|
176 |
+
# テクスチャ座標(TEXCOORD_0)
|
177 |
+
texcoord_0s = [
|
178 |
+
(0.0, 1.0),
|
179 |
+
(1.0, 1.0),
|
180 |
+
(0.0, 0.0),
|
181 |
+
(1.0, 0.0)
|
182 |
+
]
|
183 |
+
texcoord_0_bytearray = bytearray()
|
184 |
+
for texcoord_0 in texcoord_0s:
|
185 |
+
for value in texcoord_0:
|
186 |
+
texcoord_0_bytearray.extend(struct.pack('f', value))
|
187 |
+
texcoord_0_bytelen = len(texcoord_0_bytearray)
|
188 |
+
|
189 |
+
# 頂点インデックス
|
190 |
+
vertex_indices = [0, 1, 2, 1, 3, 2]
|
191 |
+
vertex_index_bytearray = bytearray()
|
192 |
+
for value in vertex_indices:
|
193 |
+
vertex_index_bytearray.extend(struct.pack('H', value))
|
194 |
+
vertex_index_bytelen = len(vertex_index_bytearray)
|
195 |
+
|
196 |
+
# バイナリデータ部分の結合
|
197 |
+
bytearray_list = [
|
198 |
+
vertex_bytearray,
|
199 |
+
normal_bytearray,
|
200 |
+
texcoord_0_bytearray,
|
201 |
+
vertex_index_bytearray,
|
202 |
+
front_bytearray,
|
203 |
+
back_bytearray,
|
204 |
+
left_side_bytearray,
|
205 |
+
right_side_bytearray,
|
206 |
+
top_side_bytearray,
|
207 |
+
bottom_side_bytearray,
|
208 |
+
]
|
209 |
+
bytelen_list = [
|
210 |
+
vertex_bytelen,
|
211 |
+
normal_bytelen,
|
212 |
+
texcoord_0_bytelen,
|
213 |
+
vertex_index_bytelen,
|
214 |
+
front_bytelen,
|
215 |
+
back_bytelen,
|
216 |
+
left_side_bytelen,
|
217 |
+
right_side_bytelen,
|
218 |
+
top_side_bytelen,
|
219 |
+
bottom_side_bytelen
|
220 |
+
]
|
221 |
+
bytelen_cumsum_list = list(np.cumsum(bytelen_list))
|
222 |
+
bytelen_cumsum_list = list(map(lambda x: int(x), bytelen_cumsum_list))
|
223 |
+
|
224 |
+
all_bytearray = bytearray()
|
225 |
+
for temp_bytearray in bytearray_list:
|
226 |
+
all_bytearray.extend(temp_bytearray)
|
227 |
+
offset_list = [0] + bytelen_cumsum_list # 最初のオフセットは0
|
228 |
+
offset_list.pop() # 末尾を削除
|
229 |
+
|
230 |
+
# リソースの作成
|
231 |
+
resources = [GLBResource(data=all_bytearray)]
|
232 |
+
|
233 |
+
# 各種設定
|
234 |
+
# バッファ
|
235 |
+
buffers = [Buffer(byteLength=len(all_bytearray))]
|
236 |
+
|
237 |
+
# バッファビュー
|
238 |
+
bufferViews = [
|
239 |
+
BufferView(buffer=0, byteOffset=offset_list[0], byteLength=bytelen_list[0], target=BufferTarget.ARRAY_BUFFER.value),
|
240 |
+
BufferView(buffer=0, byteOffset=offset_list[1], byteLength=bytelen_list[1], target=BufferTarget.ARRAY_BUFFER.value),
|
241 |
+
BufferView(buffer=0, byteOffset=offset_list[2], byteLength=bytelen_list[2], target=BufferTarget.ARRAY_BUFFER.value),
|
242 |
+
BufferView(buffer=0, byteOffset=offset_list[3], byteLength=bytelen_list[3], target=BufferTarget.ELEMENT_ARRAY_BUFFER.value),
|
243 |
+
BufferView(buffer=0, byteOffset=offset_list[4], byteLength=bytelen_list[4], target=None),
|
244 |
+
BufferView(buffer=0, byteOffset=offset_list[5], byteLength=bytelen_list[5], target=None),
|
245 |
+
BufferView(buffer=0, byteOffset=offset_list[6], byteLength=bytelen_list[6], target=None),
|
246 |
+
BufferView(buffer=0, byteOffset=offset_list[7], byteLength=bytelen_list[7], target=None),
|
247 |
+
BufferView(buffer=0, byteOffset=offset_list[8], byteLength=bytelen_list[8], target=None),
|
248 |
+
BufferView(buffer=0, byteOffset=offset_list[9], byteLength=bytelen_list[9], target=None),
|
249 |
+
]
|
250 |
+
|
251 |
+
# アクセサー
|
252 |
+
accessors = [
|
253 |
+
Accessor(bufferView=0, componentType=ComponentType.FLOAT.value, count=len(vertices), type=AccessorType.VEC3.value, max=maxs, min=mins),
|
254 |
+
Accessor(bufferView=1, componentType=ComponentType.FLOAT.value, count=len(normals), type=AccessorType.VEC3.value, max=None, min=None),
|
255 |
+
Accessor(bufferView=2, componentType=ComponentType.FLOAT.value, count=len(texcoord_0s), type=AccessorType.VEC2.value, max=None, min=None),
|
256 |
+
Accessor(bufferView=3, componentType=ComponentType.UNSIGNED_SHORT.value, count=len(vertex_indices), type=AccessorType.SCALAR.value, max=None, min=None),
|
257 |
+
]
|
258 |
+
|
259 |
+
# ノード
|
260 |
+
card_thickness = 0.025
|
261 |
+
card_ratio_x = 0.8679999709129333
|
262 |
+
card_ratio_y = 1.2130000591278076
|
263 |
+
card_ratio_z = 1
|
264 |
+
if is_thick:
|
265 |
+
|
266 |
+
nodes = [
|
267 |
+
Node(mesh=0, scale=[card_ratio_x, card_ratio_y, card_ratio_z], rotation=None, translation=[0, 0, card_thickness]),
|
268 |
+
Node(mesh=1, scale=[card_ratio_x, card_ratio_y, card_ratio_z], rotation=[0, 1, 0, 0], translation=[0, 0, -card_thickness]),
|
269 |
+
Node(mesh=2, scale=[card_thickness, card_ratio_y, card_ratio_z], rotation=[0, 0.7071, 0, 0.7071], translation=[ card_ratio_x, 0, 0]), # 左
|
270 |
+
Node(mesh=3, scale=[card_thickness, card_ratio_y, card_ratio_z], rotation=[0, -0.7071, 0, 0.7071], translation=[-card_ratio_x, 0, 0]), # 右
|
271 |
+
Node(mesh=4, scale=[card_ratio_x, card_thickness, card_ratio_z], rotation=[ 0.7071, 0, 0, 0.7071], translation=[0, card_ratio_y, 0]), # 上
|
272 |
+
Node(mesh=5, scale=[card_ratio_x, card_thickness, card_ratio_z], rotation=[-0.7071, 0, 0, 0.7071], translation=[0, -card_ratio_y, 0]), # 下
|
273 |
+
]
|
274 |
+
else:
|
275 |
+
nodes = [
|
276 |
+
Node(mesh=0, scale=[card_ratio_x, card_ratio_y, card_ratio_z], rotation=None),
|
277 |
+
Node(mesh=1, scale=[card_ratio_x, card_ratio_y, card_ratio_z], rotation=[0, 1, 0, 0])
|
278 |
+
]
|
279 |
+
|
280 |
+
# シーン
|
281 |
+
scene = 0
|
282 |
+
if is_thick:
|
283 |
+
scenes = [Scene(name='Scene', nodes=[0, 1, 2, 3, 4, 5])]
|
284 |
+
else:
|
285 |
+
scenes = [Scene(name='Scene', nodes=[0, 1])]
|
286 |
+
|
287 |
+
model = GLTFModel(
|
288 |
+
asset=asset,
|
289 |
+
buffers=buffers,
|
290 |
+
bufferViews=bufferViews,
|
291 |
+
accessors=accessors,
|
292 |
+
images=images,
|
293 |
+
samplers=samplers,
|
294 |
+
textures=textures,
|
295 |
+
materials=materials,
|
296 |
+
meshes=meshes,
|
297 |
+
nodes=nodes,
|
298 |
+
scene=scene,
|
299 |
+
scenes=scenes
|
300 |
+
)
|
301 |
+
|
302 |
+
gltf = GLTF(model=model, resources=resources)
|
303 |
+
|
304 |
+
tmp_filename = uuid.uuid4().hex
|
305 |
+
model_path = f'../tmp/{tmp_filename}.glb'
|
306 |
+
|
307 |
+
gltf.export(model_path)
|
308 |
+
|
309 |
+
return model_path
|
310 |
+
|
311 |
+
|
src/constants.py
ADDED
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
model_info_message = \
|
2 |
+
'''\
|
3 |
+
使える3Dモデルの情報です。「/info:番号」と入力すると入力情報の詳細を表示します。
|
4 |
+
1.史跡カード額縁:歴史的建造物などを飾るカードの額縁に写真を飾ります。
|
5 |
+
2.宝石額縁:きらびやかな宝石の額縁に写真を飾ります。
|
6 |
+
3.化石額縁:種々の化石の額縁に写真を飾ります。
|
7 |
+
4.猫額縁:猫のイラストの額縁に写真を飾ります。
|
8 |
+
A.写真ボックス:写真に厚みを付けて立体的にします。
|
9 |
+
B.オブジェクト切り抜き:(背景が単色であることを想定。上手くいかない場合もあります。)写真から切り抜いたオブジェクトに厚みを付けて立体的にします。
|
10 |
+
'''
|
11 |
+
|
12 |
+
upload_format_dict = \
|
13 |
+
{
|
14 |
+
'1': '/upload:1|画像ファイル名|緯度|経度|タイトル|CTユーザ名|Discordユーザ名|厚み|色|史跡種類|マーク|訪問難度|説明文',
|
15 |
+
'2': '/upload:2|画像ファイル名|緯度|経度|タイトル|CTユーザ名|Discordユーザ名|厚み|説明文',
|
16 |
+
'3': '/upload:3|画像ファイル名|緯度|経度|タイトル|CTユーザ名|Discordユーザ名|厚み|説明文',
|
17 |
+
'4': '/upload:4|画像ファイル名|緯度|経度|タイトル|CTユーザ名|Discordユーザ名|厚み|説明文',
|
18 |
+
'A': '/upload:A|画像ファイル名|緯度|経度',
|
19 |
+
'B': '/upload:B|画像ファイル名|緯度|経度',
|
20 |
+
}
|
21 |
+
|
22 |
+
model_info_detail_dict = \
|
23 |
+
{
|
24 |
+
'1': \
|
25 |
+
'''\
|
26 |
+
補足1.「厚み」は「有」、「無」のいずれかを選んでください。
|
27 |
+
補足2.「色」は以下の基準で選んでください。
|
28 |
+
「茶」: 貝塚、集落跡、古墳、墓地等
|
29 |
+
「白」: 都城跡、国郡庁、城跡、官公庁、戦跡、その他政治に関する遺跡
|
30 |
+
「橙」: 社寺跡、その他祭祀信仰に関する遺跡
|
31 |
+
「黄」: 学校、研究施設、文化施設、その他教育・学術・文化に関する遺跡
|
32 |
+
「赤」: 医療・福祉施設、生活関連施設等
|
33 |
+
「青」: 交通・通信施設、治山治水施設、生産遺跡、その他経済・生産活動に関する遺跡
|
34 |
+
「黒」: 墳墓(大名・著名人)・碑
|
35 |
+
「緑」: 旧宅、園池
|
36 |
+
「紫」: 外国及び外国人に関する遺跡
|
37 |
+
補足3.「マーク」は「国指定」、「都指定」、「道指定」、「府指定」、「県指定」、「区指定」、「市指定」、「町指定」、「村指定」のどれかから選んでください。
|
38 |
+
補足4.「訪問難度」は1から5の整数値で指定してください。
|
39 |
+
''',
|
40 |
+
'2': '補足1.「厚み」は「有」、「無」のいずれかを選んでください。',
|
41 |
+
'3': '補足1.「厚み」は「有」、「無」のいずれかを選んでください。',
|
42 |
+
'4': '補足1.「厚み」は「有」、「無」のいずれかを選んでください。',
|
43 |
+
}
|
44 |
+
|
45 |
+
def get_option_keys_from_upload_format(upload_format):
|
46 |
+
vertical_bar_indices = [i for i, char in enumerate(upload_format) if char == '|']
|
47 |
+
vertical_bar_indices.append(len(upload_format))
|
48 |
+
option_keys = [upload_format[vertical_bar_indices[i-1]+1:vertical_bar_indices[i]] for i in range(1, len(vertical_bar_indices))]
|
49 |
+
return option_keys
|
50 |
+
|
51 |
+
upload_format_option_dict = {
|
52 |
+
key: get_option_keys_from_upload_format(value)
|
53 |
+
for key, value in upload_format_dict.items()
|
54 |
+
}
|
55 |
+
|
56 |
+
upload_format_re_dict = \
|
57 |
+
{
|
58 |
+
'1': '^/upload:(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)',
|
59 |
+
'2': '^/upload:(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)',
|
60 |
+
'3': '^/upload:(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)',
|
61 |
+
'4': '^/upload:(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)\|(.*)',
|
62 |
+
'A': '^/upload:(.*)\|(.*)\|(.*)\|(.*)',
|
63 |
+
'B': '^/upload:(.*)\|(.*)\|(.*)\|(.*)',
|
64 |
+
}
|
65 |
+
|
66 |
+
front_card_img_dict = \
|
67 |
+
{
|
68 |
+
'1': 'data/cards/史跡カードフレーム(白).png', # 複数種類あり別の箇所で条件分岐で指定するが、ここではとりあえず白を指定
|
69 |
+
'2': 'data/cards/front/jewels.png',
|
70 |
+
'3': 'data/cards/front/fossils.png',
|
71 |
+
'4': 'data/cards/front/cats.png'
|
72 |
+
}
|
73 |
+
|
74 |
+
back_card_img_dict = \
|
75 |
+
{
|
76 |
+
'1': 'data/cards/史跡カード(裏面).png',
|
77 |
+
'2': 'data/cards/back/jewels.png',
|
78 |
+
'3': 'data/cards/back/fossils.png',
|
79 |
+
'4': 'data/cards/back/cats.png'
|
80 |
+
}
|
src/display_message_en.json
ADDED
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"header": "# 3D Card Creation \nPlease enter various settings, upload an image and specify its range. Then, press the \"Create 3D Card\" button. \nOnce the 3D card is completed, it will be displayed at the bottom of the screen and you will be able to download it.",
|
3 |
+
"tab_label_card_general": "Card",
|
4 |
+
"tab_label_historic_site_card": "Historic Site Card",
|
5 |
+
"label_title": "Title",
|
6 |
+
"placeholder_title": "The title displayed on the top of the card.",
|
7 |
+
"label_card_type": "Card Type",
|
8 |
+
"model_type_dict": {
|
9 |
+
"2": "Jewel",
|
10 |
+
"3": "Fossil",
|
11 |
+
"4": "Cat"
|
12 |
+
},
|
13 |
+
"label_description": "Description",
|
14 |
+
"placeholder_description": "Please provide a brief description, up to about 90 characters.",
|
15 |
+
"label_is_thick": "Thickness",
|
16 |
+
"info_is_thick": "Please check this option if you want to add thickness to the card.",
|
17 |
+
"label_image": "Image",
|
18 |
+
"label_button": "Start 3D Card Creation",
|
19 |
+
"label_color": "Color(Historic Site Classification)",
|
20 |
+
"color_dict": {
|
21 |
+
"茶": "Brown: Shell mounds, settlement ruins, ancient tombs, graveyards",
|
22 |
+
"白": "Administrative buildings, battle sites, and other political-related ruins",
|
23 |
+
"橙": "Orange: Ruins of shrines and temples, and other relics related to rituals and beliefs",
|
24 |
+
"黄": "Yellow: Schools, research facilities, cultural facilities, and other ruins related to education, academia, and culture",
|
25 |
+
"赤": "Red: Medical and welfare facilities, living-related facilities",
|
26 |
+
"青": "Blue: Transportation and communication facilities, flood control and forest management facilities, production sites etc.",
|
27 |
+
"黒": "Black: Tombs (of lords and famous persons) and monuments",
|
28 |
+
"緑": "Green: Former residences, garden ponds",
|
29 |
+
"紫": "Purple: Ruins related to foreigners and foreign countries"
|
30 |
+
},
|
31 |
+
"label_mark": "Designation Type",
|
32 |
+
"mark_list": ["国指定", "都指定", "道指定", "府指定", "県指定", "区指定", "市指定", "町指定", "村指定"],
|
33 |
+
"label_historic_site_type": "Historic Site Type",
|
34 |
+
"placeholder_historic_site_type": "ex. Statue, Monument, Site, etc.",
|
35 |
+
"label_difficulty": "Difficulty",
|
36 |
+
"footer_historic_site_card": "The images for the Historic Site Cards are borrowed from the Historic Site Card Assets on this website. \nhttps://historic-site-card.com/find"
|
37 |
+
}
|
src/display_message_ja.json
ADDED
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"header": "# 3Dカード作成 \n各種設定値の入力、画像のアップロードと範囲の指定を行った後、「3Dカード作成」ボタンを押してください。 \n3Dカードが完成したら画面下部に表示され、ダウンロードすることが出来ます。",
|
3 |
+
"tab_label_card_general": "カード(一般)",
|
4 |
+
"tab_label_historic_site_card": "史跡カード",
|
5 |
+
"label_title": "タイトル",
|
6 |
+
"placeholder_title": "カード上部に表示されるタイトルです。",
|
7 |
+
"label_card_type": "カード種類",
|
8 |
+
"model_type_dict": {
|
9 |
+
"2": "宝石",
|
10 |
+
"3": "化石",
|
11 |
+
"4": "猫"
|
12 |
+
},
|
13 |
+
"label_description": "説明文",
|
14 |
+
"placeholder_description": "全角50字程度までの簡単な説明を記載して下さい。",
|
15 |
+
"label_is_thick": "厚み",
|
16 |
+
"info_is_thick": "カードに厚みを付けるときはチェックして下さい。",
|
17 |
+
"label_image": "画像",
|
18 |
+
"label_button": "3Dカード作成",
|
19 |
+
"label_color": "色(史跡の分類)",
|
20 |
+
"color_dict": {
|
21 |
+
"茶": "「茶」: 貝塚、集落跡、古墳、墓地等",
|
22 |
+
"白": "「白」: 都城跡、国郡庁、城跡、官公庁、戦跡、その他政治に関する遺跡",
|
23 |
+
"橙": "「橙」: 社寺跡、その他祭祀信仰に関する遺跡",
|
24 |
+
"黄": "「黄」: 学校、研究施設、文化施設、その他教育・学術・文化に関する遺跡",
|
25 |
+
"赤": "「赤」: 医療・福祉施設、生活関連施設等",
|
26 |
+
"青": "「青」: 交通・通信施設、治山治水施設、生産遺跡、その他経済・生産活動に関する遺跡",
|
27 |
+
"黒": "「黒」: 墳墓(大名・著名人)・碑",
|
28 |
+
"緑": "「緑」: 旧宅、園池",
|
29 |
+
"紫": "「紫」: 外国及び外国人に関する遺跡"
|
30 |
+
},
|
31 |
+
"label_mark": "指定種類",
|
32 |
+
"mark_list": ["国指定", "都指定", "道指定", "府指定", "県指定", "区指定", "市指定", "町指定", "村指定"],
|
33 |
+
"label_historic_site_type": "史跡種類",
|
34 |
+
"placeholder_historic_site_type": "ex.銅像、石碑、記念碑、跡地、モニュメントなど。",
|
35 |
+
"label_difficulty": "訪問難度",
|
36 |
+
"footer_historic_site_card": "史跡カードの画像は、こちらのウェブサイトの史跡カードアセットをお借りしております。 \nhttps://historic-site-card.com/find"
|
37 |
+
}
|
src/extracted_objects_model.py
ADDED
@@ -0,0 +1,265 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import io
|
2 |
+
import numpy as np
|
3 |
+
from PIL import Image as PIL_Image # gltflibのImageと被るので別名にする。
|
4 |
+
import cv2
|
5 |
+
import struct
|
6 |
+
import triangle
|
7 |
+
import uuid
|
8 |
+
|
9 |
+
from gltflib import (
|
10 |
+
GLTF, GLTFModel, Asset, Scene, Node, Mesh, Primitive, Attributes, Buffer, BufferView, Image, Texture, TextureInfo, Material, Sampler, Accessor, AccessorType,
|
11 |
+
BufferTarget, ComponentType, GLBResource, PBRMetallicRoughness)
|
12 |
+
|
13 |
+
# 表面及び裏面の頂点リストの作成
|
14 |
+
def make_front_and_back_vertex_list(coordinate_list, img):
|
15 |
+
|
16 |
+
# 表面の頂点
|
17 |
+
front_vertex_list = []
|
18 |
+
# 裏面の頂点
|
19 |
+
back_vertex_list = []
|
20 |
+
for coordinates in coordinate_list:
|
21 |
+
front_vertices = []
|
22 |
+
back_vertices = []
|
23 |
+
# Y軸方向は画像とGLBで上下が逆になるので注意
|
24 |
+
for coordinate in coordinates:
|
25 |
+
front_vertices.append((coordinate[0] * 2 / img.size[0] - 1.0, -(coordinate[1] * 2 / img.size[1] - 1.0), 0.2))
|
26 |
+
back_vertices.append((coordinate[0] * 2 / img.size[0] - 1.0, -(coordinate[1] * 2 / img.size[1] - 1.0), -0.2))
|
27 |
+
|
28 |
+
front_vertex_list.append(front_vertices)
|
29 |
+
back_vertex_list.append(back_vertices)
|
30 |
+
|
31 |
+
return front_vertex_list, back_vertex_list
|
32 |
+
|
33 |
+
# メッシュの各種情報の作成
|
34 |
+
def make_mesh_data(coordinate_list, img):
|
35 |
+
front_vertex_list, back_vertex_list = make_front_and_back_vertex_list(coordinate_list, img)
|
36 |
+
|
37 |
+
# 頂点データ(POSITION)
|
38 |
+
vertices = []
|
39 |
+
# 頂点インデックス決定時に使うオフセット値のリスト
|
40 |
+
front_offset = 0
|
41 |
+
front_offset_list = []
|
42 |
+
back_offset_list = []
|
43 |
+
for front_vertices, back_vertices in zip(front_vertex_list, back_vertex_list):
|
44 |
+
vertices.extend(front_vertices)
|
45 |
+
vertices.extend(back_vertices)
|
46 |
+
|
47 |
+
back_offset = front_offset + len(front_vertices)
|
48 |
+
front_offset_list.append(front_offset)
|
49 |
+
back_offset_list.append(back_offset)
|
50 |
+
front_offset += len(front_vertices) + len(back_vertices)
|
51 |
+
|
52 |
+
# 法線データ(NORMAL)
|
53 |
+
normals = []
|
54 |
+
for front_vertices, back_vertices in zip(front_vertex_list, back_vertex_list):
|
55 |
+
normals.extend([( 0.0, 0.0, 1.0)] * len(front_vertices))
|
56 |
+
normals.extend([( 0.0, 0.0, -1.0)] * len(back_vertices))
|
57 |
+
|
58 |
+
# テクスチャ座標(TEXCOORD_0)
|
59 |
+
# 画像は左上原点になり、Y軸の上下を変える必要がある。
|
60 |
+
texcoord_0s = [((vertex[0] + 1.0) / 2.0, 1.0 - ((vertex[1] + 1.0) / 2.0) ) for vertex in vertices]
|
61 |
+
|
62 |
+
# 頂点インデックス
|
63 |
+
vertex_indices = []
|
64 |
+
for front_vertices, back_vertices, front_offset, back_offset \
|
65 |
+
in zip(front_vertex_list, back_vertex_list, front_offset_list, back_offset_list):
|
66 |
+
polygon = {
|
67 |
+
'vertices': np.array(front_vertices)[:, :2],
|
68 |
+
'segments': np.array([( i, (i + 1) % (len(front_vertices)) ) for i in range(len(front_vertices))]) # 各辺を定義
|
69 |
+
}
|
70 |
+
triangulate_result = triangle.triangulate(polygon, 'p')
|
71 |
+
vertex_indices.extend(list(np.array(triangulate_result['triangles']+front_offset).flatten())) # 表面
|
72 |
+
vertex_indices.extend(list((np.array(triangulate_result['triangles'])+back_offset).flatten())) # 裏面
|
73 |
+
vertex_indices.extend(list(np.array([[front_offset + i,
|
74 |
+
front_offset + (i + 1) % len(front_vertices),
|
75 |
+
back_offset + i]
|
76 |
+
for i in range(len(front_vertices))]).flatten())) # 側面1
|
77 |
+
vertex_indices.extend(list(np.array([[back_offset + i,
|
78 |
+
back_offset + (i + 1) % len(back_vertices),
|
79 |
+
front_offset+ (i + 1) % len(front_vertices)] for i in range(len(front_vertices))]).flatten())) # 側面2
|
80 |
+
|
81 |
+
return vertices, normals, texcoord_0s, vertex_indices
|
82 |
+
|
83 |
+
def create_extracted_objects_model(img_bytearray):
|
84 |
+
|
85 |
+
# 画像の取得
|
86 |
+
img = PIL_Image.open(img_bytearray).convert('RGB')
|
87 |
+
img_bytearray = io.BytesIO()
|
88 |
+
img.save(img_bytearray, format="JPEG", quality=95)
|
89 |
+
img_bytearray = img_bytearray.getvalue()
|
90 |
+
img_bytelen = len(img_bytearray)
|
91 |
+
|
92 |
+
# 3Dモデルのスケールの計算
|
93 |
+
scale_factor = np.power(img.size[0] * img.size[1], 0.5)
|
94 |
+
scale = (img.size[0] / scale_factor, img.size[1] / scale_factor, 0.4)
|
95 |
+
|
96 |
+
# 画像主要部分の頂点の取得
|
97 |
+
base_color = img.getpixel((0, 0))
|
98 |
+
mask = PIL_Image.new('RGB', img.size)
|
99 |
+
for i in range(img.size[0]):
|
100 |
+
for j in range(img.size[1]):
|
101 |
+
if base_color == img.getpixel((i, j)):
|
102 |
+
mask.putpixel((i, j), (0, 0, 0))
|
103 |
+
else:
|
104 |
+
mask.putpixel((i, j), (255, 255, 255))
|
105 |
+
|
106 |
+
opening = cv2.morphologyEx(np.array(mask), cv2.MORPH_OPEN, kernel=np.ones((15, 15),np.uint8))
|
107 |
+
contours, _ = cv2.findContours(cv2.cvtColor(np.array(opening), cv2.COLOR_RGB2GRAY), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_NONE)
|
108 |
+
coordinate_list = []
|
109 |
+
for contour in contours:
|
110 |
+
coordinates = []
|
111 |
+
for [[x, y]] in contour:
|
112 |
+
coordinates.append((x, y))
|
113 |
+
coordinate_list.append(coordinates)
|
114 |
+
|
115 |
+
# 各種データの作成
|
116 |
+
vertices, normals, texcoord_0s, vertex_indices = make_mesh_data(coordinate_list, img)
|
117 |
+
|
118 |
+
# 頂点データ(POSITION)
|
119 |
+
vertex_bytearray = bytearray()
|
120 |
+
for vertex in vertices:
|
121 |
+
for value in vertex:
|
122 |
+
vertex_bytearray.extend(struct.pack('f', value))
|
123 |
+
vertex_bytelen = len(vertex_bytearray)
|
124 |
+
mins = [min([vertex[i] for vertex in vertices]) for i in range(3)]
|
125 |
+
maxs = [max([vertex[i] for vertex in vertices]) for i in range(3)]
|
126 |
+
|
127 |
+
# 法線データ(NORMAL)
|
128 |
+
normal_bytearray = bytearray()
|
129 |
+
for normal in normals:
|
130 |
+
for value in normal:
|
131 |
+
normal_bytearray.extend(struct.pack('f', value))
|
132 |
+
normal_bytelen = len(normal_bytearray)
|
133 |
+
|
134 |
+
# テクスチャ座標(TEXCOORD_0)
|
135 |
+
texcoord_0s = [
|
136 |
+
((vertex[0] + 1.0) / 2.0, 1.0 - ((vertex[1] + 1.0) / 2.0) ) for vertex in vertices
|
137 |
+
]
|
138 |
+
texcoord_0_bytearray = bytearray()
|
139 |
+
for texcoord_0 in texcoord_0s:
|
140 |
+
for value in texcoord_0:
|
141 |
+
texcoord_0_bytearray.extend(struct.pack('f', value))
|
142 |
+
texcoord_0_bytelen = len(texcoord_0_bytearray)
|
143 |
+
|
144 |
+
# 頂点インデックス
|
145 |
+
vertex_index_bytearray = bytearray()
|
146 |
+
for value in vertex_indices:
|
147 |
+
vertex_index_bytearray.extend(struct.pack('H', value))
|
148 |
+
vertex_index_bytelen = len(vertex_index_bytearray)
|
149 |
+
|
150 |
+
# バイナリデータ部分の結合
|
151 |
+
bytearray_list = [
|
152 |
+
vertex_bytearray,
|
153 |
+
normal_bytearray,
|
154 |
+
texcoord_0_bytearray,
|
155 |
+
vertex_index_bytearray,
|
156 |
+
img_bytearray,
|
157 |
+
]
|
158 |
+
bytelen_list = [
|
159 |
+
vertex_bytelen,
|
160 |
+
normal_bytelen,
|
161 |
+
texcoord_0_bytelen,
|
162 |
+
vertex_index_bytelen,
|
163 |
+
img_bytelen,
|
164 |
+
]
|
165 |
+
bytelen_cumsum_list = list(np.cumsum(bytelen_list))
|
166 |
+
bytelen_cumsum_list = list(map(lambda x: int(x), bytelen_cumsum_list))
|
167 |
+
|
168 |
+
all_bytearray = bytearray()
|
169 |
+
for temp_bytearray in bytearray_list:
|
170 |
+
all_bytearray.extend(temp_bytearray)
|
171 |
+
offset_list = [0] + bytelen_cumsum_list # 最初のオフセットは0
|
172 |
+
offset_list.pop() # 末尾を削除
|
173 |
+
|
174 |
+
# リソースの作成
|
175 |
+
resources = [GLBResource(data=all_bytearray)]
|
176 |
+
|
177 |
+
# 各種設定
|
178 |
+
# アセット
|
179 |
+
asset=Asset()
|
180 |
+
|
181 |
+
# バッファ
|
182 |
+
buffers = [Buffer(byteLength=len(all_bytearray))]
|
183 |
+
|
184 |
+
# バッファビュー
|
185 |
+
bufferViews = [
|
186 |
+
BufferView(buffer=0, byteOffset=offset_list[0], byteLength=bytelen_list[0], target=BufferTarget.ARRAY_BUFFER.value),
|
187 |
+
BufferView(buffer=0, byteOffset=offset_list[1], byteLength=bytelen_list[1], target=BufferTarget.ARRAY_BUFFER.value),
|
188 |
+
BufferView(buffer=0, byteOffset=offset_list[2], byteLength=bytelen_list[2], target=BufferTarget.ARRAY_BUFFER.value),
|
189 |
+
BufferView(buffer=0, byteOffset=offset_list[3], byteLength=bytelen_list[3], target=BufferTarget.ELEMENT_ARRAY_BUFFER.value),
|
190 |
+
BufferView(buffer=0, byteOffset=offset_list[4], byteLength=bytelen_list[4], target=None),
|
191 |
+
]
|
192 |
+
|
193 |
+
# アクセサー
|
194 |
+
accessors = [
|
195 |
+
Accessor(bufferView=0, componentType=ComponentType.FLOAT.value, count=len(vertices), type=AccessorType.VEC3.value, max=maxs, min=mins),
|
196 |
+
Accessor(bufferView=1, componentType=ComponentType.FLOAT.value, count=len(normals), type=AccessorType.VEC3.value, max=None, min=None),
|
197 |
+
Accessor(bufferView=2, componentType=ComponentType.FLOAT.value, count=len(texcoord_0s), type=AccessorType.VEC2.value, max=None, min=None),
|
198 |
+
Accessor(bufferView=3, componentType=ComponentType.UNSIGNED_SHORT.value, count=len(vertex_indices), type=AccessorType.SCALAR.value, max=None, min=None)
|
199 |
+
]
|
200 |
+
|
201 |
+
# イメージ
|
202 |
+
images=[
|
203 |
+
Image(mimeType='image/jpeg', bufferView=4),
|
204 |
+
]
|
205 |
+
|
206 |
+
# サンプラー
|
207 |
+
samplers = [Sampler(magFilter=9728, minFilter=9984)] # magFilter:最近傍フィルタリング、minFilter:ミップマップ+最近傍フィルタリング
|
208 |
+
|
209 |
+
# テクスチャ
|
210 |
+
textures = [
|
211 |
+
Texture(name='Main',sampler=0,source=0),
|
212 |
+
]
|
213 |
+
|
214 |
+
# マテリアル
|
215 |
+
materials = [
|
216 |
+
Material(
|
217 |
+
pbrMetallicRoughness=PBRMetallicRoughness(
|
218 |
+
baseColorTexture=TextureInfo(index=0),
|
219 |
+
metallicFactor=0,
|
220 |
+
roughnessFactor=1
|
221 |
+
),
|
222 |
+
name='Material0',
|
223 |
+
alphaMode='OPAQUE',
|
224 |
+
doubleSided=True
|
225 |
+
),
|
226 |
+
]
|
227 |
+
|
228 |
+
# メッシュ
|
229 |
+
meshes = [
|
230 |
+
Mesh(name='Main', primitives=[Primitive(attributes=Attributes(POSITION=0, NORMAL=1,TEXCOORD_0=2),
|
231 |
+
indices=3, material=0, mode=4)]),
|
232 |
+
]
|
233 |
+
|
234 |
+
# ノード
|
235 |
+
nodes = [
|
236 |
+
Node(mesh=0,rotation=None, scale=scale),
|
237 |
+
]
|
238 |
+
|
239 |
+
# シーン
|
240 |
+
scene = 0
|
241 |
+
scenes = [Scene(name='Scene', nodes=[0])]
|
242 |
+
|
243 |
+
model = GLTFModel(
|
244 |
+
asset=asset,
|
245 |
+
buffers=buffers,
|
246 |
+
bufferViews=bufferViews,
|
247 |
+
accessors=accessors,
|
248 |
+
images=images,
|
249 |
+
samplers=samplers,
|
250 |
+
textures=textures,
|
251 |
+
materials=materials,
|
252 |
+
meshes=meshes,
|
253 |
+
nodes=nodes,
|
254 |
+
scene=scene,
|
255 |
+
scenes=scenes
|
256 |
+
)
|
257 |
+
|
258 |
+
gltf = GLTF(model=model, resources=resources)
|
259 |
+
|
260 |
+
tmp_filename = uuid.uuid4().hex
|
261 |
+
model_path = f'../tmp/{tmp_filename}.glb'
|
262 |
+
|
263 |
+
gltf.export(model_path)
|
264 |
+
|
265 |
+
return model_path
|
src/front_card_image.py
ADDED
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from PIL import Image, ImageDraw, ImageFont
|
2 |
+
from io import BytesIO
|
3 |
+
|
4 |
+
from src.constants import front_card_img_dict
|
5 |
+
|
6 |
+
# カード画像各領域のピクセル位置情報
|
7 |
+
# 写真
|
8 |
+
PICTURE_LT_XY = (65, 188)
|
9 |
+
PICTURE_RB_XY = (802, 925)
|
10 |
+
PICTURE_SIZE = (PICTURE_RB_XY[0] - PICTURE_LT_XY[0], PICTURE_RB_XY[1] - PICTURE_LT_XY[1])
|
11 |
+
|
12 |
+
# タイトル
|
13 |
+
# ある程度の余白を作る。
|
14 |
+
TITLE_LT_XY = (65, 45)
|
15 |
+
TITLE_RB_XY = (647, 132) # マーク挿入部分と重ならないような位置
|
16 |
+
TITLE_SIZE = (TITLE_RB_XY[0] - TITLE_LT_XY[0], TITLE_RB_XY[1] - TITLE_LT_XY[1])
|
17 |
+
|
18 |
+
# 説明文本体
|
19 |
+
DESCRIPTION_LT_XY = (46, 972)
|
20 |
+
DESCRIPTION_RB_XY = (810, 1174)
|
21 |
+
DESCRIPTION_SIZE = (DESCRIPTION_RB_XY[0] - DESCRIPTION_LT_XY[0], DESCRIPTION_RB_XY[1] - DESCRIPTION_LT_XY[1])
|
22 |
+
|
23 |
+
# フォント関連
|
24 |
+
# 明朝体
|
25 |
+
font_selif_path = 'data/fonts/SourceHanSerif-Bold.otf'
|
26 |
+
# ゴシック体
|
27 |
+
font_sanselif_path = 'data/fonts/SourceHanSans-Bold.otf'
|
28 |
+
|
29 |
+
def crop_center(pil_img, crop_width, crop_height):
|
30 |
+
img_width, img_height = pil_img.size
|
31 |
+
return pil_img.crop(((img_width - crop_width) // 2,
|
32 |
+
(img_height - crop_height) // 2,
|
33 |
+
(img_width + crop_width) // 2,
|
34 |
+
(img_height + crop_height) // 2))
|
35 |
+
|
36 |
+
def crop_max_square(pil_img):
|
37 |
+
return crop_center(pil_img, min(pil_img.size), min(pil_img.size))
|
38 |
+
|
39 |
+
def create_card_image(model_no, img_bytearray, option_dict):
|
40 |
+
|
41 |
+
# 画像の読み込みとトリミング
|
42 |
+
picture_img = Image.open(img_bytearray)
|
43 |
+
picture_img = crop_max_square(picture_img)
|
44 |
+
picture_img = picture_img.resize(PICTURE_SIZE)
|
45 |
+
|
46 |
+
# カードの読み込み
|
47 |
+
card_img = Image.open(front_card_img_dict[model_no])
|
48 |
+
|
49 |
+
# 写真の埋め込み
|
50 |
+
card_img.paste(picture_img, PICTURE_LT_XY)
|
51 |
+
|
52 |
+
# 各種書き込み準備
|
53 |
+
card_imgdraw = ImageDraw.Draw(card_img)
|
54 |
+
|
55 |
+
# タイトル埋め込み
|
56 |
+
# (複数行対応は必要ならば対応)
|
57 |
+
title_font_size = 100
|
58 |
+
while True:
|
59 |
+
title_font = ImageFont.truetype(font_selif_path, title_font_size)
|
60 |
+
title_bbox = card_imgdraw.textbbox(TITLE_LT_XY , option_dict['タイトル'], title_font)
|
61 |
+
|
62 |
+
if (title_bbox[2] <= TITLE_RB_XY[0] and title_bbox[3] <= TITLE_RB_XY[1]) or title_font_size <= 30:
|
63 |
+
break
|
64 |
+
title_font_size -= 1
|
65 |
+
|
66 |
+
card_imgdraw.text((TITLE_LT_XY[0], int((TITLE_LT_XY[1] + TITLE_RB_XY[1]) / 2)), option_dict['タイトル'], fill='black', font=title_font, anchor='lm')
|
67 |
+
|
68 |
+
# 説明文埋め込み
|
69 |
+
description_font = ImageFont.truetype(font_sanselif_path, 40)
|
70 |
+
|
71 |
+
description_list = []
|
72 |
+
description_length = len(option_dict['説明文'])
|
73 |
+
temp_start = 0
|
74 |
+
for i in range(description_length):
|
75 |
+
temp_end = i
|
76 |
+
description_line_bbox = card_imgdraw.textbbox((0, 0), option_dict['説明文'][temp_start:temp_end+1], description_font)
|
77 |
+
if description_line_bbox[2] > DESCRIPTION_SIZE[0]:
|
78 |
+
description_list.append(option_dict['説明文'][temp_start:temp_end])
|
79 |
+
temp_start = i
|
80 |
+
|
81 |
+
description_list.append(option_dict['説明文'][temp_start:])
|
82 |
+
description_display = '\n'.join(description_list)
|
83 |
+
|
84 |
+
card_imgdraw.text(DESCRIPTION_LT_XY, description_display, fill='black', font=description_font)
|
85 |
+
|
86 |
+
# バイナリデータの出力
|
87 |
+
output_img_bytearray = BytesIO()
|
88 |
+
card_img.convert('RGB').save(output_img_bytearray, "JPEG", quality=95)
|
89 |
+
output_img_bytearray.seek(0) # 画像の先頭にシークしないと空データになってしまう。
|
90 |
+
|
91 |
+
return output_img_bytearray
|
src/front_card_image_historic_site.py
ADDED
@@ -0,0 +1,150 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from PIL import Image, ImageDraw, ImageFont
|
2 |
+
from io import BytesIO
|
3 |
+
|
4 |
+
# カード画像関連
|
5 |
+
card_frame_path_dict = {
|
6 |
+
'橙': 'data/cards/史跡カードフレーム(橙).png',
|
7 |
+
'白': 'data/cards/史跡カードフレーム(白).png',
|
8 |
+
'紫': 'data/cards/史跡カードフレーム(紫).png',
|
9 |
+
'緑': 'data/cards/史跡カードフレーム(緑).png',
|
10 |
+
'茶': 'data/cards/史跡カードフレーム(茶).png',
|
11 |
+
'赤': 'data/cards/史跡カードフレーム(赤).png',
|
12 |
+
'青': 'data/cards/史跡カードフレーム(青).png',
|
13 |
+
'黄': 'data/cards/史跡カードフレーム(黄).png',
|
14 |
+
'黒': 'data/cards/史跡カードフレーム(黒).png'
|
15 |
+
}
|
16 |
+
card_mark_path_dict = {
|
17 |
+
'区指定': 'data/cards/史跡カード指定マーク(区指定).png',
|
18 |
+
'国指定': 'data/cards/史跡カード指定マーク(国指定).png',
|
19 |
+
'市指定': 'data/cards/史跡カード指定マーク(市指定).png',
|
20 |
+
'府指定': 'data/cards/史跡カード指定マーク(府指定).png',
|
21 |
+
'村指定': 'data/cards/史跡カード指定マーク(村指定).png',
|
22 |
+
'町指定': 'data/cards/史跡カード指定マーク(町指定).png',
|
23 |
+
'県指定': 'data/cards/史跡カード指定マーク(県指定).png',
|
24 |
+
'道指定': 'data/cards/史跡カード指定マーク(道指定).png',
|
25 |
+
'都指定': 'data/cards/史跡カード指定マーク(都指定).png'
|
26 |
+
}
|
27 |
+
|
28 |
+
# カード画像各領域のピクセル位置情報
|
29 |
+
# 写真
|
30 |
+
PICTURE_LT_XY = (65, 188)
|
31 |
+
PICTURE_RB_XY = (802, 925)
|
32 |
+
PICTURE_SIZE = (PICTURE_RB_XY[0] - PICTURE_LT_XY[0], PICTURE_RB_XY[1] - PICTURE_LT_XY[1])
|
33 |
+
|
34 |
+
# タイトル
|
35 |
+
# ある程度の余白を作る。
|
36 |
+
TITLE_LT_XY = (65, 45)
|
37 |
+
TITLE_RB_XY = (647, 132) # マーク挿入部分と重ならないような位置
|
38 |
+
TITLE_SIZE = (TITLE_RB_XY[0] - TITLE_LT_XY[0], TITLE_RB_XY[1] - TITLE_LT_XY[1])
|
39 |
+
|
40 |
+
# 説明欄区切り線
|
41 |
+
DESCRIPTION_LINE_L_XY = (52, 1024)
|
42 |
+
DESCRIPTION_LINE_R_XY = (816, 1024)
|
43 |
+
|
44 |
+
# 史跡種類
|
45 |
+
HS_TYPE_LT_XY = (56, 972)
|
46 |
+
|
47 |
+
# 訪問難度
|
48 |
+
DIFFICULTY_LT_XY = (444, 972)
|
49 |
+
|
50 |
+
# 説明文本体
|
51 |
+
DESCRIPTION_LT_XY = (46, 1024)
|
52 |
+
DESCRIPTION_RB_XY = (810, 1174)
|
53 |
+
DESCRIPTION_SIZE = (DESCRIPTION_RB_XY[0] - DESCRIPTION_LT_XY[0], DESCRIPTION_RB_XY[1] - DESCRIPTION_LT_XY[1])
|
54 |
+
|
55 |
+
|
56 |
+
# フォント関連
|
57 |
+
# 明朝体
|
58 |
+
font_selif_path = 'data/fonts/SourceHanSerif-Bold.otf'
|
59 |
+
# ゴシック体
|
60 |
+
font_sanselif_path = 'data/fonts/SourceHanSans-Bold.otf'
|
61 |
+
|
62 |
+
def crop_center(pil_img, crop_width, crop_height):
|
63 |
+
img_width, img_height = pil_img.size
|
64 |
+
return pil_img.crop(((img_width - crop_width) // 2,
|
65 |
+
(img_height - crop_height) // 2,
|
66 |
+
(img_width + crop_width) // 2,
|
67 |
+
(img_height + crop_height) // 2))
|
68 |
+
|
69 |
+
def crop_max_square(pil_img):
|
70 |
+
return crop_center(pil_img, min(pil_img.size), min(pil_img.size))
|
71 |
+
|
72 |
+
def create_historic_site_card_image(img_bytearray, option_dict):
|
73 |
+
|
74 |
+
# 画像の読み込みとトリミング
|
75 |
+
picture_img = Image.open(img_bytearray)
|
76 |
+
picture_img = crop_max_square(picture_img)
|
77 |
+
picture_img = picture_img.resize(PICTURE_SIZE)
|
78 |
+
|
79 |
+
|
80 |
+
# カードの読み込み
|
81 |
+
card_img = Image.open(card_frame_path_dict[option_dict['色']])
|
82 |
+
|
83 |
+
# マークの追加
|
84 |
+
mark_image = Image.open(card_mark_path_dict[option_dict['マーク']])
|
85 |
+
card_img.paste(mark_image, mask=mark_image)
|
86 |
+
|
87 |
+
# 写真の埋め込み
|
88 |
+
card_img.paste(picture_img, PICTURE_LT_XY)
|
89 |
+
|
90 |
+
# 各種書き込み準備
|
91 |
+
card_imgdraw = ImageDraw.Draw(card_img)
|
92 |
+
|
93 |
+
# カード枠線の追加
|
94 |
+
card_imgdraw.line(( (0, 0), (card_img.size[0], 0) ), fill='black', width=15)
|
95 |
+
card_imgdraw.line(( (0, 0), (0, card_img.size[1]) ), fill='black', width=15)
|
96 |
+
card_imgdraw.line(( (card_img.size[0], 0), card_img.size), fill='black', width=15)
|
97 |
+
card_imgdraw.line(( (0, card_img.size[1]), card_img.size), fill='black', width=15)
|
98 |
+
|
99 |
+
# 説明欄区切り線の追加
|
100 |
+
card_imgdraw.line((DESCRIPTION_LINE_L_XY, DESCRIPTION_LINE_R_XY), fill='black', width=3)
|
101 |
+
|
102 |
+
# タイトル埋め込み
|
103 |
+
# (複数行対応は必要ならば対応)
|
104 |
+
title_font_size = 100
|
105 |
+
while True:
|
106 |
+
title_font = ImageFont.truetype(font_selif_path, title_font_size)
|
107 |
+
title_bbox = card_imgdraw.textbbox(TITLE_LT_XY , option_dict['タイトル'], title_font)
|
108 |
+
|
109 |
+
if (title_bbox[2] <= TITLE_RB_XY[0] and title_bbox[3] <= TITLE_RB_XY[1]) or title_font_size <= 30:
|
110 |
+
break
|
111 |
+
title_font_size -= 1
|
112 |
+
|
113 |
+
card_imgdraw.text((TITLE_LT_XY[0], int((TITLE_LT_XY[1] + TITLE_RB_XY[1]) / 2)), option_dict['タイトル'], fill='black', font=title_font, anchor='lm')
|
114 |
+
|
115 |
+
# 史跡種類埋め込み
|
116 |
+
hs_type_display = f'種類:{option_dict["史跡種類"]}'
|
117 |
+
hs_typefont = ImageFont.truetype(font_sanselif_path, 40)
|
118 |
+
card_imgdraw.text(HS_TYPE_LT_XY, hs_type_display, fill='black', font=hs_typefont, anchor='lt')
|
119 |
+
|
120 |
+
# 訪問難度埋め込み
|
121 |
+
difficulty = int(option_dict['訪問難度'])
|
122 |
+
difficulty = 1 if difficulty < 1 else 5 if difficulty > 5 else difficulty
|
123 |
+
difficulty_display = '訪問難度:' + '☆' * difficulty + '★' * (5 - difficulty)
|
124 |
+
difficulty_font = ImageFont.truetype(font_sanselif_path, 40)
|
125 |
+
card_imgdraw.text(DIFFICULTY_LT_XY, difficulty_display, fill='black', font=difficulty_font, anchor='lt')
|
126 |
+
|
127 |
+
# 説明文埋め込み
|
128 |
+
description_font = ImageFont.truetype(font_sanselif_path, 40)
|
129 |
+
|
130 |
+
description_list = []
|
131 |
+
description_length = len(option_dict['説明文'])
|
132 |
+
temp_start = 0
|
133 |
+
for i in range(description_length):
|
134 |
+
temp_end = i
|
135 |
+
description_line_bbox = card_imgdraw.textbbox((0, 0), option_dict['説明文'][temp_start:temp_end+1], description_font)
|
136 |
+
if description_line_bbox[2] > DESCRIPTION_SIZE[0]:
|
137 |
+
description_list.append(option_dict['説明文'][temp_start:temp_end])
|
138 |
+
temp_start = i
|
139 |
+
|
140 |
+
description_list.append(option_dict['説明文'][temp_start:])
|
141 |
+
description_display = '\n'.join(description_list)
|
142 |
+
|
143 |
+
card_imgdraw.text(DESCRIPTION_LT_XY, description_display, fill='black', font=description_font)
|
144 |
+
|
145 |
+
# バイナリデータの出力
|
146 |
+
output_img_bytearray = BytesIO()
|
147 |
+
card_img.convert('RGB').save(output_img_bytearray, "JPEG", quality=95)
|
148 |
+
output_img_bytearray.seek(0) # 画像の先頭にシークしないと空データになってしまう。
|
149 |
+
|
150 |
+
return output_img_bytearray
|
src/picture_box_model.py
ADDED
@@ -0,0 +1,265 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import io
|
2 |
+
import numpy as np
|
3 |
+
|
4 |
+
from PIL import Image as PIL_Image # gltflibのImageと被るので別名にする。
|
5 |
+
import struct
|
6 |
+
import uuid
|
7 |
+
|
8 |
+
from gltflib import (
|
9 |
+
GLTF, GLTFModel, Asset, Scene, Node, Mesh, Primitive, Attributes, Buffer, BufferView, Image, Texture, TextureInfo, Material, Sampler, Accessor, AccessorType,
|
10 |
+
BufferTarget, ComponentType, GLBResource, PBRMetallicRoughness)
|
11 |
+
|
12 |
+
# 共通バイナリ情報
|
13 |
+
# 頂点データ(POSITION)
|
14 |
+
vertices = [
|
15 |
+
[ 1.0, 1.0, -1.0,],
|
16 |
+
[ 1.0, 1.0, -1.0,],
|
17 |
+
[ 1.0, 1.0, -1.0,],
|
18 |
+
[ 1.0, -1.0, -1.0,],
|
19 |
+
[ 1.0, -1.0, -1.0,],
|
20 |
+
[ 1.0, -1.0, -1.0,],
|
21 |
+
[ 1.0, 1.0, 1.0,],
|
22 |
+
[ 1.0, 1.0, 1.0,],
|
23 |
+
[ 1.0, 1.0, 1.0,],
|
24 |
+
[ 1.0, -1.0, 1.0,],
|
25 |
+
[ 1.0, -1.0, 1.0,],
|
26 |
+
[ 1.0, -1.0, 1.0,],
|
27 |
+
[-1.0, 1.0, -1.0,],
|
28 |
+
[-1.0, 1.0, -1.0,],
|
29 |
+
[-1.0, 1.0, -1.0,],
|
30 |
+
[-1.0, -1.0, -1.0,],
|
31 |
+
[-1.0, -1.0, -1.0,],
|
32 |
+
[-1.0, -1.0, -1.0,],
|
33 |
+
[-1.0, 1.0, 1.0,],
|
34 |
+
[-1.0, 1.0, 1.0,],
|
35 |
+
[-1.0, 1.0, 1.0,],
|
36 |
+
[-1.0, -1.0, 1.0,],
|
37 |
+
[-1.0, -1.0, 1.0,],
|
38 |
+
[-1.0, -1.0, 1.0,],
|
39 |
+
]
|
40 |
+
vertex_bytearray = bytearray()
|
41 |
+
for vertex in vertices:
|
42 |
+
for value in vertex:
|
43 |
+
vertex_bytearray.extend(struct.pack('f', value))
|
44 |
+
vertex_bytelen = len(vertex_bytearray)
|
45 |
+
mins = [min([vertex[i] for vertex in vertices]) for i in range(3)]
|
46 |
+
maxs = [max([vertex[i] for vertex in vertices]) for i in range(3)]
|
47 |
+
|
48 |
+
# 法線データ(NORMAL)
|
49 |
+
normals = [
|
50 |
+
[ 0.0, 0.0, -1.0,],
|
51 |
+
[ 0.0, 1.0, -0.0,],
|
52 |
+
[ 1.0, 0.0, -0.0,],
|
53 |
+
[ 0.0, -1.0, -0.0,],
|
54 |
+
[ 0.0, 0.0, -1.0,],
|
55 |
+
[ 1.0, 0.0, -0.0,],
|
56 |
+
[ 0.0, 0.0, 1.0,],
|
57 |
+
[ 0.0, 1.0, -0.0,],
|
58 |
+
[ 1.0, 0.0, -0.0,],
|
59 |
+
[ 0.0, -1.0, -0.0,],
|
60 |
+
[ 0.0, 0.0, 1.0,],
|
61 |
+
[ 1.0, 0.0, -0.0,],
|
62 |
+
[-1.0, 0.0, -0.0,],
|
63 |
+
[ 0.0, 0.0, -1.0,],
|
64 |
+
[ 0.0, 1.0, -0.0,],
|
65 |
+
[-1.0, 0.0, -0.0,],
|
66 |
+
[ 0.0, -1.0, -0.0,],
|
67 |
+
[ 0.0, 0.0, -1.0,],
|
68 |
+
[-1.0, 0.0, -0.0,],
|
69 |
+
[ 0.0, 0.0, 1.0,],
|
70 |
+
[ 0.0, 1.0, -0.0,],
|
71 |
+
[-1.0, 0.0, -0.0,],
|
72 |
+
[ 0.0, -1.0, -0.0,],
|
73 |
+
[ 0.0, 0.0, 1.0,],
|
74 |
+
]
|
75 |
+
normal_bytearray = bytearray()
|
76 |
+
for normal in normals:
|
77 |
+
for value in normal:
|
78 |
+
normal_bytearray.extend(struct.pack('f', value))
|
79 |
+
normal_bytelen = len(normal_bytearray)
|
80 |
+
|
81 |
+
# テクスチャ座標(TEXCOORD_0)
|
82 |
+
texcoord_0s = [
|
83 |
+
[0.9, 0.1],[0.9, 0.0],[1.0, 0.1],
|
84 |
+
[0.9, 1.0],[0.9, 0.9],[1.0, 0.9],
|
85 |
+
[0.9, 0.1],[0.9, 0.1],[0.9, 0.1],
|
86 |
+
[0.9, 0.9],[0.9, 0.9],[0.9, 0.9],
|
87 |
+
[0.0, 0.1],[0.1, 0.1],[0.1, 0.0],
|
88 |
+
[0.0, 0.9],[0.1, 1.0],[0.1, 0.9],
|
89 |
+
[0.1, 0.1],[0.1, 0.1],[0.1, 0.1],
|
90 |
+
[0.1, 0.9],[0.1, 0.9],[0.1, 0.9],
|
91 |
+
]
|
92 |
+
texcoord_0_bytearray = bytearray()
|
93 |
+
for texcoord_0 in texcoord_0s:
|
94 |
+
for value in texcoord_0:
|
95 |
+
texcoord_0_bytearray.extend(struct.pack('f', value))
|
96 |
+
texcoord_0_bytelen = len(texcoord_0_bytearray)
|
97 |
+
|
98 |
+
# 頂点インデックス
|
99 |
+
vertex_indices = [
|
100 |
+
1, 14, 20,
|
101 |
+
1, 20, 7,
|
102 |
+
10, 6, 19,
|
103 |
+
10, 19, 23,
|
104 |
+
21, 18, 12,
|
105 |
+
21, 12, 15,
|
106 |
+
16, 3, 9,
|
107 |
+
16, 9, 22,
|
108 |
+
5, 2, 8,
|
109 |
+
5, 8, 11,
|
110 |
+
17, 13, 0,
|
111 |
+
17, 0, 4
|
112 |
+
]
|
113 |
+
|
114 |
+
vertex_index_bytearray = bytearray()
|
115 |
+
for value in vertex_indices:
|
116 |
+
vertex_index_bytearray.extend(struct.pack('H', value))
|
117 |
+
vertex_index_bytelen = len(vertex_index_bytearray)
|
118 |
+
|
119 |
+
def create_picture_box_model(img_bytearray):
|
120 |
+
|
121 |
+
# 画像の取得
|
122 |
+
img = PIL_Image.open(img_bytearray).convert('RGB')
|
123 |
+
|
124 |
+
# 辺の長さが8で割れる数値になるように調整
|
125 |
+
img = img.resize((8 * (img.size[0] // 8), 8 * (img.size[1] // 8)))
|
126 |
+
temp_img = PIL_Image.new('RGB', (int(img.size[0] * 1.25), int(img.size[1] * 1.25)), 'white')
|
127 |
+
temp_img.paste(img, (int(temp_img.size[0] / 10), int(temp_img.size[1] / 10)))
|
128 |
+
img = temp_img.copy()
|
129 |
+
|
130 |
+
for offset_x in range(0, int(temp_img.size[0] / 10)):
|
131 |
+
img.paste(img.crop((int(temp_img.size[0] / 10)+1, 0, int(temp_img.size[0] / 10)+2, img.size[1])), (offset_x, 0))
|
132 |
+
for offset_x in range(int(temp_img.size[0] / 10 * 9), img.size[0]):
|
133 |
+
img.paste(img.crop((int(temp_img.size[0] / 10 * 9) - 2, 0, int(temp_img.size[0] / 10 *9) - 1, img.size[1])), (offset_x, 0))
|
134 |
+
|
135 |
+
for offset_y in range(0, int(temp_img.size[1] / 10)):
|
136 |
+
img.paste(img.crop((0, int(temp_img.size[1] / 10) + 1, img.size[0], int(temp_img.size[1] / 10) + 2)), (0, offset_y))
|
137 |
+
for offset_y in range(int(temp_img.size[1] / 10 * 9), img.size[1]):
|
138 |
+
img.paste(img.crop((0, int(temp_img.size[1] / 10 * 9) - 2, img.size[0], int(temp_img.size[1] / 10 * 9) - 1)), (0, offset_y))
|
139 |
+
|
140 |
+
img_bytearray = io.BytesIO()
|
141 |
+
img.save(img_bytearray, format="JPEG", quality=95)
|
142 |
+
img_bytearray = img_bytearray.getvalue()
|
143 |
+
img_bytelen = len(img_bytearray)
|
144 |
+
|
145 |
+
# 3Dモデルのスケールの計算
|
146 |
+
scale_factor = np.power(img.size[0] * img.size[1], 0.5)
|
147 |
+
scale = (img.size[0] / scale_factor, img.size[1] / scale_factor, 0.1)
|
148 |
+
|
149 |
+
# バイナリデータ部分の結合
|
150 |
+
bytearray_list = [
|
151 |
+
vertex_bytearray,
|
152 |
+
normal_bytearray,
|
153 |
+
texcoord_0_bytearray,
|
154 |
+
vertex_index_bytearray,
|
155 |
+
img_bytearray,
|
156 |
+
]
|
157 |
+
bytelen_list = [
|
158 |
+
vertex_bytelen,
|
159 |
+
normal_bytelen,
|
160 |
+
texcoord_0_bytelen,
|
161 |
+
vertex_index_bytelen,
|
162 |
+
img_bytelen,
|
163 |
+
]
|
164 |
+
bytelen_cumsum_list = list(np.cumsum(bytelen_list))
|
165 |
+
bytelen_cumsum_list = list(map(lambda x: int(x), bytelen_cumsum_list))
|
166 |
+
|
167 |
+
all_bytearray = bytearray()
|
168 |
+
for temp_bytearray in bytearray_list:
|
169 |
+
all_bytearray.extend(temp_bytearray)
|
170 |
+
offset_list = [0] + bytelen_cumsum_list # 最初のオフセットは0
|
171 |
+
offset_list.pop() # 末尾を削除
|
172 |
+
|
173 |
+
# リソースの作成
|
174 |
+
resources = [GLBResource(data=all_bytearray)]
|
175 |
+
|
176 |
+
# 各種設定
|
177 |
+
# アセット
|
178 |
+
asset=Asset()
|
179 |
+
|
180 |
+
# バッファ
|
181 |
+
buffers = [Buffer(byteLength=len(all_bytearray))]
|
182 |
+
|
183 |
+
# バッファビュー
|
184 |
+
bufferViews = [
|
185 |
+
BufferView(buffer=0, byteOffset=offset_list[0], byteLength=bytelen_list[0], target=BufferTarget.ARRAY_BUFFER.value),
|
186 |
+
BufferView(buffer=0, byteOffset=offset_list[1], byteLength=bytelen_list[1], target=BufferTarget.ARRAY_BUFFER.value),
|
187 |
+
BufferView(buffer=0, byteOffset=offset_list[2], byteLength=bytelen_list[2], target=BufferTarget.ARRAY_BUFFER.value),
|
188 |
+
BufferView(buffer=0, byteOffset=offset_list[3], byteLength=bytelen_list[3], target=BufferTarget.ELEMENT_ARRAY_BUFFER.value),
|
189 |
+
BufferView(buffer=0, byteOffset=offset_list[4], byteLength=bytelen_list[4], target=None),
|
190 |
+
]
|
191 |
+
|
192 |
+
# アクセサー
|
193 |
+
accessors = [
|
194 |
+
Accessor(bufferView=0, componentType=ComponentType.FLOAT.value, count=len(vertices), type=AccessorType.VEC3.value, max=maxs, min=mins),
|
195 |
+
Accessor(bufferView=1, componentType=ComponentType.FLOAT.value, count=len(normals), type=AccessorType.VEC3.value, max=None, min=None),
|
196 |
+
Accessor(bufferView=2, componentType=ComponentType.FLOAT.value, count=len(texcoord_0s), type=AccessorType.VEC2.value, max=None, min=None),
|
197 |
+
Accessor(bufferView=3, componentType=ComponentType.UNSIGNED_SHORT.value, count=len(vertex_indices), type=AccessorType.SCALAR.value, max=None, min=None)
|
198 |
+
]
|
199 |
+
|
200 |
+
# イメージ
|
201 |
+
images=[
|
202 |
+
Image(mimeType='image/jpeg', bufferView=4),
|
203 |
+
]
|
204 |
+
|
205 |
+
# サンプラー
|
206 |
+
samplers = [Sampler(magFilter=9728, minFilter=9984)] # magFilter:最近傍フィルタリング、minFilter:ミップマップ+最近傍フィルタリング
|
207 |
+
|
208 |
+
# テクスチャ
|
209 |
+
textures = [
|
210 |
+
Texture(name='Image',sampler=0,source=0),
|
211 |
+
]
|
212 |
+
|
213 |
+
# マテリアル
|
214 |
+
materials = [
|
215 |
+
Material(
|
216 |
+
pbrMetallicRoughness=PBRMetallicRoughness(
|
217 |
+
baseColorTexture=TextureInfo(index=0),
|
218 |
+
metallicFactor=0,
|
219 |
+
roughnessFactor=0.5
|
220 |
+
),
|
221 |
+
name='Material0',
|
222 |
+
alphaMode='BLEND',
|
223 |
+
doubleSided=False
|
224 |
+
),
|
225 |
+
]
|
226 |
+
|
227 |
+
# メッシュ
|
228 |
+
meshes = [
|
229 |
+
Mesh(name='Image', primitives=[Primitive(attributes=Attributes(POSITION=0, NORMAL=1,TEXCOORD_0=2),indices=3, material=0)]),
|
230 |
+
]
|
231 |
+
|
232 |
+
# ノード
|
233 |
+
nodes = [
|
234 |
+
Node(mesh=0,rotation=None, scale=scale),
|
235 |
+
]
|
236 |
+
|
237 |
+
# シーン
|
238 |
+
scene = 0
|
239 |
+
scenes = [Scene(name='Scene', nodes=[0])]
|
240 |
+
|
241 |
+
model = GLTFModel(
|
242 |
+
asset=asset,
|
243 |
+
buffers=buffers,
|
244 |
+
bufferViews=bufferViews,
|
245 |
+
accessors=accessors,
|
246 |
+
images=images,
|
247 |
+
samplers=samplers,
|
248 |
+
textures=textures,
|
249 |
+
materials=materials,
|
250 |
+
meshes=meshes,
|
251 |
+
nodes=nodes,
|
252 |
+
scene=scene,
|
253 |
+
scenes=scenes
|
254 |
+
)
|
255 |
+
|
256 |
+
gltf = GLTF(model=model, resources=resources)
|
257 |
+
|
258 |
+
tmp_filename = uuid.uuid4().hex
|
259 |
+
model_path = f'../tmp/{tmp_filename}.glb'
|
260 |
+
|
261 |
+
gltf.export(model_path)
|
262 |
+
|
263 |
+
return model_path
|
264 |
+
|
265 |
+
|