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