-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathworldmap-generator.js
207 lines (177 loc) · 6.11 KB
/
worldmap-generator.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
const sharp = require('sharp');
const fs = require('fs');
const path = require('path');
const INPUT_DIR = 'images/';
const IMAGE_SIZE = 512;
// 出力されるファイル名は worldmap-YYYY-MM-DD.png のような形式になります。
const OUTPUT_FILE_NAME = 'worldmap';
const OUTPUT_EXTENSION = 'png';
const OUTPUT_DIR = 'output/'
const allImages = fs.readdirSync(INPUT_DIR);
if (allImages.length === 0) {
console.error('Error: No images found in the input directory.');
process.exit(1);
}
// 定期的にメモリ使用量を吐き出す
setInterval(() => {
const used = process.memoryUsage()
const messages = []
for (let key in used) {
messages.push(`${key}: ${Math.round(used[key] / 1024 / 1024 * 100) / 100} MB`)
}
console.log(new Date(), messages.join(', '))
}, 1000)
filterValidImages(allImages).then(validImages => {
console.log('Filtering valid images conpleted');
console.log('Valid images are: ' + validImages)
const gridSize = getGridSize(validImages);
sharp.cache(false);
sharp.concurrency(1);
createWorldMap(validImages, gridSize);
});
async function filterValidImages(images) {
console.log('Filtering Valid Images...')
let validImages = [];
for (const image of images) {
const isError = await isErrorImage(path.join(INPUT_DIR, image), 16, 1);
if (!isError) {
validImages.push(image); // エラー画像でなければ追加
console.log('A valid image added!')
}
}
console.log(validImages)
return validImages;
}
async function isErrorImage(image, headerHeight, transparencyThreshold) {
console.log(`Checking image: ${image}`)
// 画像のピクセルデータと情報を取得
// 返値の形式は
// {
// data: <Buffer 3c 3c 3c ff 36 36 36 ff 36 36 36 ff 3a 3a 3a ff 43 43 43 ff 43 43 43 ff 3d 3d 3d ff 3d 3d 3d ff 3d 3d 3d ff 40 40 40 ff 40 40 40 ff 45 45 45 ff 3c 3c ... 14100430 more bytes>,
// info: {
// format: 'raw',
// width: 2560,
// height: 1377,
// channels: 4,
// depth: 'uchar',
// premultiplied: false,
// size: 14100480
// }
// }
const { data, info } = await sharp(image)
.ensureAlpha()
.raw()
.toBuffer({ resolveWithObject: true });
const { width, height } = info;
const dataArray = Array.from(data); // どうしてArrayLikeにはmap()メソッドがないのか、コレガワカラナイ
// const hexDataArray = decimalToHexArray(dataArray); カラーコードが分かりやすいように16進数にする。今回はいらないのでコメントアウトしとく
const pixels = chunkArray(dataArray, 4);
const rows = chunkArray(pixels, width);
let transparent = 0;
let total = 0;
for (let y = headerHeight; y < height; y++) {
for (let x = 0; x < width; x++) {
const a = rows[y][x][3]
if (a === 0) transparent++;
total++;
}
}
const transparentRate = transparent / total;
return transparentRate >= transparencyThreshold;
}
function getGridSize(images) {
console.log('Getting grid size...')
let minX = Infinity,
minY = Infinity,
maxX = -Infinity,
maxY = -Infinity;
const gridSize = [];
images.forEach(image => {
// 1,1.png のような形式の画像のファイル名からXY座標を取得
const x = parseInt(parseFileName(image).x, 10);
const y = parseInt(parseFileName(image).y, 10);
minX = Math.min(minX, x);
minY = Math.min(minY, y);
maxX = Math.max(maxX, x);
maxY = Math.max(maxY, y);
});
gridSize[0] = maxX - minX + 1;
gridSize[1] = maxY - minY + 1;
console.log(`Grid size: ${gridSize[0]}x${gridSize[1]}`);
return {
sizeX: gridSize[0],
sizeY: gridSize[1],
minX: minX,
minY: minY,
maxX: maxX,
maxY: maxY
}
}
async function createWorldMap(images, gridSize) {
const time = new Date();
const timeStamp = `${time.getFullYear()}-${time.getMonth() + 1}-${time.getDate()}`;
const output = `${OUTPUT_FILE_NAME}-${timeStamp}.${OUTPUT_EXTENSION}`;
console.log(`Creating world map: ${output}`);
const tiles = images.map(image => {
const { x, y } = parseFileName(image);
const top = y - gridSize.minY;
const left = x - gridSize.minX;
return {
input: path.join(INPUT_DIR, image),
top: top * IMAGE_SIZE,
left: left * IMAGE_SIZE
};
});
console.log('Generating tile data is completed');
try {
console.log('Generating worldmap...')
await sharp({
create: {
width: gridSize.sizeX * IMAGE_SIZE,
height: gridSize.sizeY * IMAGE_SIZE,
channels: 4,
background: { r: 255, g: 255, b: 255, alpha: 1 }
},
limitInputPixels: false
})
.composite(tiles)
.toFile(path.join(OUTPUT_DIR, output));
console.log(`Created ${output}`);
} catch (err) {
console.error('Error:', err);
} finally {
console.log('Closing process...')
process.exit(0);
}
}
/**
* JourneyMapの地図の画像データの名前情報を解析
* @param {string} fileName 解析するファイルパス
* @returns {object} ext: 拡張子, name: 拡張子を含まないファイル名, x: X座標, y: Y座標
*/
function parseFileName(fileName) {
console.log(`Parsing file name: ${fileName}`);
const parsed = path.parse(fileName);
const ext = parsed.ext;
const name = parsed.name;
const [x, y] = name.split(',');
return {
x: x,
y: y,
name: name,
ext: ext
}
}
function chunkArray(arr, n) {
const result = [];
for (let i = 0; i < arr.length; i += n) {
result.push(arr.slice(i, i + n));
}
return result;
}
// Bufferの状態だと16進数なのになぜか取り出すと10進数になってしまします。どうして。
function decimalToHexArray(array) {
return array.map(decimal => {
return decimal.toString(16);
})
}