1 /*
2 This file is part of Warzone 2100.
3 Copyright (C) 1999-2004 Eidos Interactive
4 Copyright (C) 2005-2020 Warzone 2100 Project
5
6 Warzone 2100 is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 Warzone 2100 is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with Warzone 2100; if not, write to the Free Software
18 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20 #include "lib/framework/frame.h"
21
22 #include "lib/framework/frameresource.h"
23 #include "lib/framework/file.h"
24
25 #include "bitimage.h"
26 #include "tex.h"
27
28 #include <set>
29 #include <algorithm>
30 #include <unordered_map>
31 #include <vector>
32
33 static std::unordered_map<WzString, ImageDef *> images;
34 static std::vector<IMAGEFILE *> files;
35
36 struct ImageMergeRectangle
37 {
operator <ImageMergeRectangle38 bool operator <(ImageMergeRectangle const &b) const
39 {
40 if (std::max(siz.x, siz.y) != std::max(b.siz.x, b.siz.y))
41 {
42 return std::max(siz.x, siz.y) < std::max(b.siz.x, b.siz.y);
43 }
44 if (std::min(siz.x, siz.y) != std::min(b.siz.x, b.siz.y))
45 {
46 return std::min(siz.x, siz.y) < std::min(b.siz.x, b.siz.y);
47 }
48 return siz.x < b.siz.x;
49 }
50
51 int index; // Index in ImageDefs array.
52 size_t page; // Texture page index.
53 Vector2i loc = Vector2i(0, 0), siz = Vector2i(0, 0);
54 iV_Image *data;
55 };
56
57 struct ImageMerge
58 {
59 static const int pageSize = 256;
60
61 void arrange();
62
63 std::vector<ImageMergeRectangle> images;
64 std::vector<int> pages; // List of page sizes, normally all pageSize, unless an image is too large for a normal page.
65 };
66
iV_GetImage(const WzString & filename)67 ImageDef *iV_GetImage(const WzString &filename)
68 {
69 auto it = images.find(filename);
70 if (it == images.end())
71 {
72 debug(LOG_ERROR, "%s not found in image list!", filename.toUtf8().c_str());
73 return nullptr;
74 }
75 return it->second;
76 }
77
78 // Used to provide empty space between sprites arranged on the page, to avoid display glitches when scaling + drawing
79 #define SPRITE_BUFFER_PIXELS 1
80
checkRect(const ImageMergeRectangle & rect,const int pageSize)81 void checkRect(const ImageMergeRectangle& rect, const int pageSize)
82 {
83 if ((rect.loc.x + rect.siz.x) > pageSize)
84 {
85 debug(LOG_ERROR, "Merge rectangle bounds extend outside of pageSize bounds: %d > %d", (rect.loc.x + rect.siz.x), pageSize);
86 }
87 if ((rect.loc.y + rect.siz.y) > pageSize)
88 {
89 debug(LOG_ERROR, "Merge rectangle bounds extend outside of pageSize bounds: %d > %d", (rect.loc.y + rect.siz.y), pageSize);
90 }
91 assert(rect.loc.x >= 0);
92 assert(rect.loc.y >= 0);
93 }
94
arrange()95 inline void ImageMerge::arrange()
96 {
97 std::multiset<ImageMergeRectangle> freeSpace;
98 pages.clear();
99
100 std::sort(images.begin(), images.end());
101
102 std::vector<ImageMergeRectangle>::iterator r = images.end();
103 while (r != images.begin())
104 {
105 --r;
106
107 std::multiset<ImageMergeRectangle>::iterator f = freeSpace.lower_bound(*r); // Find smallest free rectangle which is large enough.
108 while (f != freeSpace.end() && (f->siz.x < r->siz.x || f->siz.y < r->siz.y))
109 {
110 ++f; // Rectangle has wrong shape.
111 }
112 if (f == freeSpace.end())
113 {
114 // No free space, make new page.
115 int s = pageSize;
116 while (s < (r->siz.x | r->siz.y))
117 {
118 s *= 2;
119 }
120 ImageMergeRectangle newPage;
121 newPage.page = pages.size();
122 newPage.loc = Vector2i(0, 0);
123 newPage.siz = Vector2i(s, s);
124 pages.push_back(s);
125 f = freeSpace.insert(newPage);
126 }
127 r->page = f->page;
128 r->loc = f->loc;
129 ImageMergeRectangle spRight;
130 ImageMergeRectangle spDown;
131 spRight.page = f->page;
132 spDown.page = f->page;
133 spRight.loc = f->loc + Vector2i(r->siz.x + SPRITE_BUFFER_PIXELS, 0);
134 spDown.loc = f->loc + Vector2i(0, r->siz.y + SPRITE_BUFFER_PIXELS);
135 spRight.siz = Vector2i(f->siz.x - (r->siz.x + SPRITE_BUFFER_PIXELS), r->siz.y);
136 spDown.siz = Vector2i(r->siz.x, f->siz.y - (r->siz.y + SPRITE_BUFFER_PIXELS));
137 if (spRight.siz.x <= spDown.siz.y)
138 {
139 // Split horizontally.
140 spDown.siz.x = f->siz.x;
141 }
142 else
143 {
144 // Split vertically.
145 spRight.siz.y = f->siz.y;
146 }
147 checkRect(spDown, pages.at(spDown.page));
148 checkRect(spRight, pages.at(spRight.page));
149 if (spRight.siz.x > 0 && spRight.siz.y > 0)
150 {
151 freeSpace.insert(spRight);
152 }
153 if (spDown.siz.x > 0 && spDown.siz.y > 0)
154 {
155 freeSpace.insert(spDown);
156 }
157 freeSpace.erase(f);
158 }
159 }
160
iV_LoadImageFile(const char * fileName)161 IMAGEFILE *iV_LoadImageFile(const char *fileName)
162 {
163 // Find the directory of images.
164 std::string imageDir = fileName;
165 if (imageDir.find_last_of('.') != std::string::npos)
166 {
167 imageDir.erase(imageDir.find_last_of('.'));
168 }
169 imageDir += '/';
170
171 // Load the image list file.
172 char *pFileData;
173 unsigned pFileSize;
174 if (!loadFile(fileName, &pFileData, &pFileSize))
175 {
176 debug(LOG_ERROR, "iV_LoadImageFile: failed to open %s", fileName);
177 return nullptr;
178 }
179
180 char *ptr = pFileData;
181 // count lines, which is identical to number of images
182 int numImages = 0;
183 while (ptr < pFileData + pFileSize && *ptr != '\0')
184 {
185 numImages += *ptr == '\n';
186 ++ptr;
187 }
188 IMAGEFILE *imageFile = new IMAGEFILE;
189 imageFile->imageDefs.resize(numImages);
190 imageFile->imageNames.resize(numImages);
191 ImageMerge pageLayout;
192 pageLayout.images.resize(numImages);
193 ptr = pFileData;
194 numImages = 0;
195 while (ptr < pFileData + pFileSize)
196 {
197 int temp, retval;
198 ImageDef *ImageDef = &imageFile->imageDefs[numImages];
199
200 char tmpName[256];
201 retval = sscanf(ptr, "%d,%d,%255[^\r\n\",]%n", &ImageDef->XOffset, &ImageDef->YOffset, tmpName, &temp);
202 if (retval != 3)
203 {
204 debug(LOG_ERROR, "Bad line in \"%s\".", fileName);
205 delete imageFile;
206 free(pFileData);
207 return nullptr;
208 }
209 imageFile->imageNames[numImages].first = tmpName;
210 imageFile->imageNames[numImages].second = numImages;
211 std::string spriteName = imageDir + tmpName;
212
213 ImageMergeRectangle *imageRect = &pageLayout.images[numImages];
214 imageRect->index = numImages;
215 imageRect->data = new iV_Image;
216 if (!iV_loadImage_PNG(spriteName.c_str(), imageRect->data))
217 {
218 debug(LOG_ERROR, "Failed to find image \"%s\" listed in \"%s\".", spriteName.c_str(), fileName);
219 delete imageFile;
220 free(pFileData);
221 return nullptr;
222 }
223 imageRect->siz = Vector2i(imageRect->data->width, imageRect->data->height);
224 numImages++;
225 ptr += temp;
226 while (ptr < pFileData + pFileSize && *ptr++ != '\n') {} // skip rest of line
227
228 images.insert(std::make_pair(WzString::fromUtf8(tmpName), ImageDef));
229 }
230 free(pFileData);
231
232 std::sort(imageFile->imageNames.begin(), imageFile->imageNames.end());
233
234 pageLayout.arrange(); // Arrange all the images onto texture pages (attempt to do so with as few pages as possible).
235 imageFile->pages.resize(pageLayout.pages.size());
236
237 std::vector<iV_Image> ivImages(pageLayout.pages.size());
238
239 for (unsigned p = 0; p < pageLayout.pages.size(); ++p)
240 {
241 int size = pageLayout.pages[p];
242 ivImages[p].depth = 4;
243 ivImages[p].width = size;
244 ivImages[p].height = size;
245 ivImages[p].bmp = (unsigned char *)malloc(size * size * 4); // MUST be malloc, since this is free()d later by pie_AddTexPage().
246 memset(ivImages[p].bmp, 0x00, size * size * 4);
247 imageFile->pages[p].size = size;
248 // Must set imageFile->pages[p].id later, after filling out ivImages[p].bmp.
249 }
250
251 for (std::vector<ImageMergeRectangle>::const_iterator r = pageLayout.images.begin(); r != pageLayout.images.end(); ++r)
252 {
253 imageFile->imageDefs[r->index].TPageID = r->page;
254 imageFile->imageDefs[r->index].Tu = r->loc.x;
255 imageFile->imageDefs[r->index].Tv = r->loc.y;
256 imageFile->imageDefs[r->index].Width = r->siz.x;
257 imageFile->imageDefs[r->index].Height = r->siz.y;
258
259 // Copy image data onto texture page.
260 iV_Image *srcImage = r->data;
261 int srcDepth = srcImage->depth;
262 int srcStride = srcImage->width * srcDepth; // Not sure whether to pad in the case that srcDepth ≠ 4, however this apparently cannot happen.
263 unsigned char *srcBytes = srcImage->bmp + 0 * srcDepth + 0 * srcStride;
264 iV_Image *dstImage = &ivImages[r->page];
265 int dstDepth = dstImage->depth;
266 int dstStride = dstImage->width * dstDepth;
267 unsigned char *dstBytes = dstImage->bmp + r->loc.x * dstDepth + r->loc.y * dstStride;
268 Vector2i size = r->siz;
269 unsigned char rgba[4] = {0x00, 0x00, 0x00, 0xFF};
270 for (int y = 0; y < size.y; ++y)
271 for (int x = 0; x < size.x; ++x)
272 {
273 memcpy(rgba, srcBytes + x * srcDepth + y * srcStride, srcDepth);
274 memcpy(dstBytes + x * dstDepth + y * dstStride, rgba, dstDepth);
275 }
276
277 // Finished reading the image data and copying it to the texture page, delete it.
278 free(r->data->bmp);
279 delete r->data;
280 }
281
282 // Debug usage only. Dump all images to disk (do mkdir images/, first). Doesn't dump the alpha channel, since the .ppm format doesn't support that.
283 /*for (unsigned p = 0; p < pageLayout.pages.size(); ++p)
284 {
285 char fName[100];
286 sprintf(fName, "%s-%d", fileName, p);
287 printf("Dump %s\n", fName);
288 FILE *f = fopen(fName, "wb");
289 iV_Image *image = &ivImages[p];
290 fprintf(f, "P6\n%d %d\n255\n", image->width, image->height);
291 for (int x = 0; x < image->width*image->height; ++x)
292 if (fwrite(image->bmp + x*4, 3, 1, f) == 0)
293 abort();
294 fclose(f);
295 }*/
296
297 // Upload texture pages and free image data.
298 for (unsigned p = 0; p < pageLayout.pages.size(); ++p)
299 {
300 char arbitraryName[256];
301 ssprintf(arbitraryName, "%s-%03u", fileName, p);
302 // Now we can set imageFile->pages[p].id. This free()s the ivImages[p].bmp array!
303 imageFile->pages[p].id = pie_AddTexPage(&ivImages[p], arbitraryName, false);
304 }
305
306 // duplicate some data, since we want another access point to these data structures now, FIXME
307 for (unsigned i = 0; i < imageFile->imageDefs.size(); i++)
308 {
309 imageFile->imageDefs[i].textureId = imageFile->pages[imageFile->imageDefs[i].TPageID].id;
310 imageFile->imageDefs[i].invTextureSize = 1.f / imageFile->pages[imageFile->imageDefs[i].TPageID].size;
311 }
312
313 files.push_back(imageFile);
314
315 return imageFile;
316 }
317
iV_FreeImageFile(IMAGEFILE * imageFile)318 void iV_FreeImageFile(IMAGEFILE *imageFile)
319 {
320 // so when we get here, it is time to redo everything. will clean this up later. TODO.
321 files.clear();
322 images.clear();
323 delete imageFile;
324 }
325
find(std::string const & name)326 Image IMAGEFILE::find(std::string const &name)
327 {
328 std::pair<std::string, int> val(name, 0);
329 std::vector<std::pair<std::string, int>>::const_iterator i = std::lower_bound(imageNames.begin(), imageNames.end(), val);
330 if (i != imageNames.end() && i->first == name)
331 {
332 return Image(this, i->second);
333 }
334 return Image(this, 0); // Error, image not found.
335 }
336