Skip to content

Commit ef074b4

Browse files
committed
use sharp for image processing
1 parent 00a8b30 commit ef074b4

10 files changed

+1257
-189
lines changed

fonts/Bender.otf

51.3 KB
Binary file not shown.

fonts/fonts.conf

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0"?>
2+
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
3+
<fontconfig>
4+
<dir>./</dir>
5+
<config></config>
6+
</fontconfig>

generate.js

+147-72
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const path = require('path');
55
const process = require('process');
66
const EventEmitter = require('events');
77
const got = require('got');
8-
const Jimp = require('jimp-compact');
8+
const sharp = require('sharp');
99

1010
const uploadImages = require('./upload-images');
1111
const hashCalc = require('./hash-calculator');
@@ -28,7 +28,8 @@ const getIcon = async (filename, item, options) => {
2828
}
2929

3030
const filepath = path.join(options.filePath || iconCacheFolder, filename);
31-
const sourceImage = await Jimp.read(filepath);
31+
const sourceImage = sharp(filepath);
32+
const sourceMeta = await sourceImage.metadata();
3233

3334
if (!options.response.generated[item.id]) options.response.generated[item.id] = [];
3435
if (!options.response.uploaded[item.id]) options.response.uploaded[item.id] = [];
@@ -39,13 +40,15 @@ const getIcon = async (filename, item, options) => {
3940
if (options.generateOnlyMissing && !item.needsBaseImage) {
4041
return resolve(false);
4142
}
42-
resolve(imageFunctions.createBaseImage(sourceImage, item).then(result => {
43+
resolve(imageFunctions.createBaseImage(sourceImage, item).then(async baseImage => {
44+
const baseImagePath = path.join('./', 'generated-images', `${item.id}-base-image.png`);
45+
await baseImage.toFile(baseImagePath);
4346
options.response.generated[item.id].push('base');
4447
if (item.needsBaseImage && options.upload){
4548
console.log(`${item.id} should be uploaded for base-image`);
4649
fs.copyFileSync(result.path, path.join('./', 'generated-images-missing', `${item.id}-base-image.png`));
4750
}
48-
return {path: result.path, type: 'base'};
51+
return {path: baseImagePath, type: 'base'};
4952
}));
5053
});
5154

@@ -54,13 +57,15 @@ const getIcon = async (filename, item, options) => {
5457
if (options.generateOnlyMissing && !item.needsIconImage) {
5558
return resolve(false);
5659
}
57-
resolve(imageFunctions.createIcon(sourceImage, item).then(result => {
60+
resolve(imageFunctions.createIcon(sourceImage, item).then(async iconImage => {
61+
const iconPath = path.join('./', 'generated-images', `${item.id}-icon.jpg`);
62+
await iconImage.toFile(iconPath);
5863
options.response.generated[item.id].push('icon');
5964
if (item.needsIconImage && options.upload) {
6065
console.log(`${item.id} should be uploaded for icon`);
61-
fs.copyFileSync(result.path, path.join('./', 'generated-images-missing', `${item.id}-icon.jpg`));
66+
fs.copyFileSync(iconPath, path.join('./', 'generated-images-missing', `${item.id}-icon.jpg`));
6267
}
63-
return {path: result.path, type: 'icon'};
68+
return {path: iconPath, type: 'icon'};
6469
}));
6570
});
6671

@@ -69,52 +74,85 @@ const getIcon = async (filename, item, options) => {
6974
if (options.generateOnlyMissing && !item.needsGridImage) {
7075
return resolve(false);
7176
}
72-
resolve(imageFunctions.createGridImage(sourceImage, item).then(result => {
77+
resolve(imageFunctions.createGridImage(sourceImage, item).then(async gridImage => {
78+
const gridImagePath = path.join('./', 'generated-images', `${item.id}-grid-image.jpg`);
79+
await gridImage.toFile(gridImagePath);
7380
options.response.generated[item.id].push('grid image');
7481
if (item.needsGridImage && options.upload) {
7582
console.log(`${item.id} should be uploaded for grid-image`);
76-
fs.copyFileSync(result.path, path.join('./', 'generated-images-missing', `${item.id}-grid-image.jpg`));
83+
fs.copyFileSync(gridImagePath, path.join('./', 'generated-images-missing', `${item.id}-grid-image.jpg`));
7784
}
78-
return {path: result.path, type: 'grid'};
85+
return {path: gridImagePath, type: 'grid'};
7986
}));
8087
});
8188

82-
// create large image
83-
const largeImagePromise = new Promise(resolve => {
84-
if (options.generateOnlyMissing && !item.needsLargeImage) {
89+
// create inspect image
90+
const inspectImagePromise = new Promise(resolve => {
91+
if (options.generateOnlyMissing && !item.needsInspectImage) {
8592
return resolve(false);
8693
}
87-
resolve(imageFunctions.createLargeImage(sourceImage, item).then(result => {
88-
options.response.generated[item.id].push('large image');
89-
if (item.needsLargeImage && options.upload) {
90-
console.log(`${item.id} should be uploaded for large image`);
91-
fs.copyFileSync(result.path, path.join('./', 'generated-images-missing', `${item.id}-large.png`));
94+
resolve(imageFunctions.createInspectImage(sourceImage, item).then(async inspectImage => {
95+
const inspectImagePath = path.join('./', 'generated-images', `${item.id}-image.jpg`);
96+
await inspectImage.toFile(inspectImagePath);
97+
options.response.generated[item.id].push('inspect image');
98+
if (item.needsInspectImage && options.upload) {
99+
console.log(`${item.id} should be uploaded for inspect image`);
100+
fs.copyFileSync(inspectImagePath, path.join('./', 'generated-images-missing', `${item.id}-image.jpg`));
92101
}
93-
return {path: result.path, type: 'large'};
102+
return {path: inspectImagePath, type: 'image'};
94103
}).catch(error => {
95-
console.log(`Error creating large image for ${item.id}`, error);
104+
console.log(`Error creating inspect image for ${item.id}`, error);
96105
return false;
97106
}));
98107
});
99108

100-
// create inspect image
101-
const inspectImagePromise = new Promise(resolve => {
102-
if (options.generateOnlyMissing && !item.needsInspectImage) {
109+
// create 512 image
110+
const largeImagePromise = new Promise(async resolve => {
111+
if (options.generateOnlyMissing && !item.needs512Image) {
103112
return resolve(false);
104113
}
105-
resolve(imageFunctions.createInspectImage(sourceImage, item).then(result => {
106-
options.response.generated[item.id].push('inspect image');
107-
if (item.needsLargeImage && options.upload) {
108-
console.log(`${item.id} should be uploaded for inspect image`);
109-
fs.copyFileSync(result.path, path.join('./', 'generated-images-missing', `${item.id}-image.jpg`));
114+
if (!await imageFunctions.canCreate512Image(sourceMeta)) {
115+
return resolve(false);
116+
}
117+
resolve(imageFunctions.create512Image(sourceImage, item).then(async largeImage => {
118+
const largeImagePath = path.join('./', 'generated-images', `${item.id}-512.webp`);
119+
await largeImage.toFile(largeImagePath);
120+
options.response.generated[item.id].push('512 image');
121+
if (item.needs512Image && options.upload) {
122+
console.log(`${item.id} should be uploaded for 512 image`);
123+
fs.copyFileSync(largeImagePath, path.join('./', 'generated-images-missing', `${item.id}-512.webp`));
110124
}
111-
return {path: result.path, type: 'image'};
125+
return {path: largeImagePath, type: '512'};
112126
}).catch(error => {
113-
console.log(`Error creating inspect image for ${item.id}`, error);
127+
console.log(`Error creating 512 image for ${item.id}`, error);
114128
return false;
115129
}));
116130
});
117-
return Promise.all([baseImagePromise, iconPromise, gridImagePromise, largeImagePromise, inspectImagePromise]).then(results => {
131+
132+
//create 8x image
133+
const xlImagePromise = new Promise(async resolve => {
134+
if (options.generateOnlyMissing && !item.needs8xImage) {
135+
return resolve(false);
136+
}
137+
if (!await imageFunctions.canCreate8xImage(sourceMeta, item)) {
138+
return resolve(false);
139+
}
140+
resolve(imageFunctions.create8xImage(sourceImage, item).then(async xlImage => {
141+
const imageFilename = imageFunctions.getImageName(item, '8x');
142+
const xlImagePath = path.join('./', 'generated-images', imageFilename);
143+
await xlImage.toFile(xlImagePath);
144+
options.response.generated[item.id].push('8x image');
145+
if (item.needs8xImage && options.upload) {
146+
console.log(`${item.id} should be uploaded for 8x image`);
147+
fs.copyFileSync(xlImagePath, path.join('./', 'generated-images-missing', imageFilename));
148+
}
149+
return {path: xlImagePath, type: '8x'};
150+
}).catch (error => {
151+
console.log(`Error creating 8x image for ${item.id}`, error);
152+
}));
153+
});
154+
155+
return Promise.all([baseImagePromise, iconPromise, gridImagePromise, largeImagePromise, inspectImagePromise, xlImagePromise]).then(results => {
118156
return results.filter(Boolean);
119157
}).catch(error => {
120158
console.log(error);
@@ -241,7 +279,6 @@ const hashItems = async (options) => {
241279
responseType: 'json',
242280
resolveBodyOnly: true
243281
});
244-
hashCalc.init(bsgData, bsgPresets, presets);
245282
let missingGridImage = 0;
246283
let missingIcon = 0;
247284
let missingBaseImage = 0;
@@ -254,7 +291,8 @@ const hashItems = async (options) => {
254291
itemData.needsIconImage = false;
255292
itemData.needsBaseImage = false;
256293
itemData.needsInspectImage = false;
257-
itemData.needsLargeImage = false;
294+
itemData.needs512Image = false;
295+
itemData.needs8xImage = false;
258296
if (itemData.gridImageLink.includes('unknown-item')) {
259297
itemData.needsGridImage = true;
260298
missingGridImage++;
@@ -296,6 +334,63 @@ const hashItems = async (options) => {
296334
}
297335
};
298336

337+
const getItemWithHash = async (options) => {
338+
let item = false;
339+
if (options.targetItemId) {
340+
if (!itemsById[options.targetItemId]) {
341+
await hashItems(options);
342+
}
343+
item = itemsById[options.targetItemId];
344+
} else {
345+
item = options.item;
346+
options.targetItemId = item.id;
347+
if (!item.backgroundColor) {
348+
setBackgroundColor(item);
349+
}
350+
try {
351+
item.hash = hashCalc.getItemHash(item.id);
352+
if (!itemsByHash[item.hash.toString()]) {
353+
itemsByHash[item.hash.toString()] = item;
354+
}
355+
} catch (error) {
356+
console.log(`Error hashing ${item.id}: ${error}`);
357+
}
358+
}
359+
return item;
360+
};
361+
362+
const getIconCacheNumberForItem = async (item, options) => {
363+
options = {
364+
cacheUpdateTimeout: 0,
365+
...options
366+
};
367+
if ((Array.isArray(item.types) && (item.types.includes('gun') || item.types.includes('preset')))) {
368+
return Promise.reject(new Error('Cannot hash weapons and presets'));
369+
}
370+
const hash = item.hash;
371+
if (!hash) return Promise.reject(new Error(`Item ${item.id} has no hash`));
372+
if (!iconData[hash]) {
373+
await new Promise((resolve, reject) => {
374+
const cacheUpdateFunc = () => {
375+
if (iconData[hash]) {
376+
clearTimeout(cacheUpdateTimeout);
377+
cacheListener.off(cacheUpdateFunc);
378+
resolve();
379+
}
380+
};
381+
const cacheUpdateTimeout = setTimeout(() => {
382+
if (iconData[hash]) {
383+
cacheListener.off(cacheUpdateFunc);
384+
return resolve();
385+
}
386+
reject(new Error(`Item ${item.id} hash ${hash} not found in cache`));
387+
}, options.cacheUpdateTimeout);
388+
cacheListener.on('refresh', cacheUpdateFunc);
389+
});
390+
}
391+
return iconData[hash];
392+
};
393+
299394
const initialize = async (options) => {
300395
const defaultOptions = {
301396
skipHashing: false
@@ -311,6 +406,7 @@ const initialize = async (options) => {
311406
await loadBsgData(opts);
312407
await loadPresets(opts);
313408
await loadBsgPresets(opts);
409+
hashCalc.init(bsgData, bsgPresets, presets);
314410
if (!options.skipHashing) {
315411
await hashItems(opts);
316412
}
@@ -354,6 +450,7 @@ const generate = async (options, forceImageIndex) => {
354450
if (!bsgPresets) {
355451
await loadBsgPresets(options);
356452
}
453+
hashCalc.init(bsgData, bsgPresets, presets);
357454
if (!cacheIsLoaded()) {
358455
refreshCache();
359456
}
@@ -383,47 +480,13 @@ const generate = async (options, forceImageIndex) => {
383480
}
384481

385482
if (options.targetItemId || options.item) {
386-
let item = false;
387-
if (options.targetItemId) {
388-
if (!itemsById[options.targetItemId]) {
389-
await hashItems(options);
390-
}
391-
item = itemsById[options.targetItemId];
392-
} else {
393-
item = options.item;
394-
options.targetItemId = item.id;
395-
if (!item.backgroundColor) {
396-
setBackgroundColor(item);
397-
}
398-
try {
399-
hashCalc.init(bsgData, bsgPresets, presets);
400-
item.hash = hashCalc.getItemHash(item.id);
401-
if (!itemsByHash[item.hash.toString()]) {
402-
itemsByHash[item.hash.toString()] = item;
403-
}
404-
} catch (error) {
405-
console.log(`Error hashing ${item.id}: ${error}`);
406-
}
407-
}
408-
if (!item) return Promise.reject(new Error(`Item ${options.targetItemId} is unknown`));
483+
let item = await getItemWithHash(options);
484+
if (!item) return Promise.reject(new Error(`Item ${options.targetItemId || options.item.id} is unknown`));
409485
let fileName = `${options.forceImageIndex}.png`;
410486
if (!options.forceImageIndex) {
411487
const hash = item.hash;
412-
if (!hash) return Promise.reject(new Error(`Item ${options.targetItemId} has no hash`));
413-
if (!iconData[hash]) {
414-
try {
415-
if (options.cacheUpdateTimeout === false || (Array.isArray(item.types) && item.types.includes('gun'))) {
416-
throw new Error('not found');
417-
}
418-
await cacheChanged(options.cacheUpdateTimeout);
419-
if (!iconData[hash]) {
420-
throw new Error('not found');
421-
}
422-
} catch (error) {
423-
return Promise.reject(new Error(`Item ${options.targetItemId} hash ${hash} not found in cache`));
424-
}
425-
}
426-
fileName = `${iconData[hash]}.png`;
488+
if (!hash) return Promise.reject(new Error(`Item ${options.targetItemId || options.item.id} has no hash`));
489+
fileName = `${await getIconCacheNumberForItem(item, options)}.png`;
427490
}
428491
try {
429492
await getIcon(fileName, item, options);
@@ -518,5 +581,17 @@ module.exports = {
518581
}
519582
},
520583
getImagesFromSource: getIcon,
521-
imageFunctions: imageFunctions
584+
imageFunctions: imageFunctions,
585+
getIconCachePath: async (options) => {
586+
if (typeof options === 'string')
587+
options = {targetItemId: options};
588+
let item = await getItemWithHash(options);
589+
if (!item)
590+
return Promise.reject(new Error(`Item ${options.targetItemId || options.item.id} is unknown`));
591+
const hash = item.hash;
592+
if (!hash)
593+
return Promise.reject(new Error(`Item ${options.targetItemId || options.item.id} has no hash`));
594+
const filename = `${await getIconCacheNumberForItem(item, options)}.png`;
595+
return path.join(options.filePath || iconCacheFolder, filename);
596+
}
522597
};

get-json.js

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ module.exports = {
1717
try {
1818
if (process.env.API_USERNAME && process.env.API_PASSWORD && process.env.SCANNER_NAME) {
1919
items = await api.getJson('items.json').catch(() => { return false; });
20+
console.log('got items from api')
2021
}
2122
if (!items) {
2223
console.log('Downloading SPT item data');

0 commit comments

Comments
 (0)