-
Notifications
You must be signed in to change notification settings - Fork 1
/
BitmapBatchExtractor.cpp
147 lines (122 loc) · 5.26 KB
/
BitmapBatchExtractor.cpp
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
#include "BitmapBatchExtractor.h"
// from https://dev.gnupg.org/T4539
#ifdef _WIN64
#define strerror_r(errnum, buf, buflen) strerror_s(buf, buflen, errnum)
#endif
#include <png++/png.hpp>
#include <thread>
#include <sstream>
#include <iomanip>
BitmapBatchExtractor::BitmapBatchExtractor(ModuleManager* modman, Logger* logger){
this->modman = modman;
this->logger = logger;
}
void BitmapBatchExtractor::extract(std::string path, std::vector<uint32_t> bitmaps, int mipmap, int threadcount){
if(threadcount == 0){
threadcount = std::thread::hardware_concurrency();
}
if(threadcount == 0){
logger->log(LOG_LEVEL_WARNING, "std::thread::hardware_concurrency() reports 0, using 4 threads as fallback!\n");
threadcount = 4;
} else {
logger->log(LOG_LEVEL_INFO, "Exporting textures with %d threads\n", threadcount);
}
logger->log(LOG_LEVEL_INFO, "Exporting %d textures to %s\n", bitmaps.size(), path.c_str());
bitmapWorkerControl* workerControls = new bitmapWorkerControl[threadcount];
std::vector<std::thread> workerThreads;
workerThreads.resize(threadcount);
// start the worker threads
for(int i = 0; i < threadcount; i++){
workerControls[i].status = bitmapWorkerControl::WORKER_IDLE;
workerControls[i].kill = false;
workerControls[i].mipmap = mipmap;
workerThreads[i] = std::thread(bitmapWorkerLoop, modman, &workerControls[i], nullptr);
}
int threadcounter = 0;
for(auto globalId : bitmaps){
if(!modman->assetIdItems.contains(globalId)){
logger->log(LOG_LEVEL_ERROR, "Bitmap 0x%08x cannot be found in the loaded modules!\n", globalId);
continue;
}
if(globalId == 0xffffffff){
continue;
}
bool nextBtm = false;
while(!nextBtm){
for(; threadcounter < threadcount; threadcounter++){
if(workerControls[threadcounter].status == bitmapWorkerControl::WORKER_IDLE){
// give the work to this one
workerControls[threadcounter].globalId = globalId;
std::stringstream stream;
stream << path << "/" << std::hex << std::setw(8) << std::setfill('0') << globalId; // only png for now
workerControls[threadcounter].path = stream.str();
workerControls[threadcounter].status = bitmapWorkerControl::WORKER_NEW_WORK; // all parameters have been set
nextBtm = true;
break; // continue on the next global ID
}
}
if(threadcounter == threadcount){
threadcounter = 0; // start over again with the first thread
}
}
}
// set all the worker threads to terminate once they're done
for(int i = 0; i < threadcount; i++){
workerControls[i].kill = true; // stop the thread once it's idle again
}
// these two loops could be combined, but this way the threads might stop a bit faster
for(int i = 0; i < threadcount; i++){
workerThreads[i].join();
}
}
void BitmapBatchExtractor::bitmapWorkerLoop(ModuleManager* modman, bitmapWorkerControl* control, Logger* logger){
control->isRunning = true;
while(!control->kill){
// main loop of the thread
if(control->status == bitmapWorkerControl::WORKER_IDLE){
std::this_thread::yield();
}
if(control->status == bitmapWorkerControl::WORKER_NEW_WORK){
control->status = bitmapWorkerControl::WORKER_WORKING; // both states mean no modification of control data from other threads, but just to be sure
if(!modman->assetIdItems.contains(control->globalId)){
control->status = bitmapWorkerControl::WORKER_IDLE; // ready for new work, control values can now be modified
printf("Global ID not available\n");
continue;
}
// the logger might be problematic, as I don't think that's really thread safe (at least not the GUI version)
BitmapHandle bitmapHandle(modman->assetIdItems[control->globalId],logger);
if(bitmapHandle.frameCount == 0 || bitmapHandle.frames[0].mipmapCount == 0){
continue;
}
int idx = 0;
for(;idx < bitmapHandle.frameCount; idx++){
int lvl = 0;
lvl = std::min((uint32_t)control->mipmap, bitmapHandle.frames[0].mipmapCount-1);
void* data = bitmapHandle.frames[idx].getR8G8B8A8Data(lvl);
if(data == nullptr){
continue;
}
// export that image data as PNG
png::image<png::rgba_pixel> pngimg(bitmapHandle.frames[idx].mipMaps[lvl].width, bitmapHandle.frames[idx].mipMaps[lvl].height);
png::image<png::rgba_pixel>::pixbuf pixbuf(bitmapHandle.frames[idx].mipMaps[lvl].width, bitmapHandle.frames[idx].mipMaps[lvl].height);
// copy the pixel data
for(size_t y = 0; y < bitmapHandle.frames[idx].mipMaps[lvl].height; y++){
for(size_t x = 0; x < bitmapHandle.frames[idx].mipMaps[lvl].width; x++){
size_t offset = ((y * bitmapHandle.frames[idx].mipMaps[lvl].width) + x) * 4;
pngimg.set_pixel(x, y, png::rgba_pixel(((char*)data)[offset], ((char*)data)[offset+1], ((char*)data)[offset+2], ((char*)data)[offset+3]));
}
}
pngimg.write((idx == 0 ? control->path : control->path + "_" + std::to_string(idx)) + ".png");
/*if(!stbi_write_png(control->path.c_str(), bitmapHandle.frames[idx].mipMaps[lvl].width, bitmapHandle.frames[idx].mipMaps[lvl].height,
4, data, bitmapHandle.frames[idx].mipMaps[lvl].width * 4)){
// write failed
printf("png export failed\n");
}*/
free(data); // no memory leaks with image data please
}
// done
control->status = bitmapWorkerControl::WORKER_IDLE; // ready for new work, control values can now be modified
}
}
control->isRunning = false;
}