1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 1998-2000, Matthes Bender
5  * Copyright (c) 2001-2009, RedWolf Design GmbH, http://www.clonk.de/
6  * Copyright (c) 2009-2016, The OpenClonk Team and contributors
7  *
8  * Distributed under the terms of the ISC license; see accompanying file
9  * "COPYING" for details.
10  *
11  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
12  * See accompanying file "TRADEMARK" for details.
13  *
14  * To redistribute this file separately, substitute the full license texts
15  * for the above references.
16  */
17 
18 /* Extension to C4Surface that handles bitmaps in C4Group files */
19 
20 #include "C4Include.h"
21 #include "graphics/C4Surface.h"
22 
23 #include "c4group/C4GroupSet.h"
24 #include "c4group/C4Group.h"
25 #include "graphics/StdPNG.h"
26 #include "lib/StdColors.h"
27 
LoadAny(C4Group & hGroup,const char * szName,bool fOwnPal,bool fNoErrIfNotFound,int iFlags)28 bool C4Surface::LoadAny(C4Group &hGroup, const char *szName, bool fOwnPal, bool fNoErrIfNotFound, int iFlags)
29 {
30 	// Entry name
31 	char szFilename[_MAX_FNAME+1];
32 	SCopy(szName,szFilename,_MAX_FNAME);
33 	char *szExt = GetExtension(szFilename);
34 	if (!*szExt)
35 	{
36 		// no extension: Default to extension that is found as file in group
37 		const char * const extensions[] = { "png", "bmp", "jpeg", "jpg", nullptr };
38 		int i = 0; const char *szExt;
39 		while ((szExt = extensions[i++]))
40 		{
41 			EnforceExtension(szFilename, szExt);
42 			if (hGroup.FindEntry(szFilename)) break;
43 		}
44 	}
45 	// Load surface
46 	return Load(hGroup,szFilename,fOwnPal,fNoErrIfNotFound,iFlags);
47 }
48 
49 
LoadAny(C4GroupSet & hGroupset,const char * szName,bool fOwnPal,bool fNoErrIfNotFound,int iFlags)50 bool C4Surface::LoadAny(C4GroupSet &hGroupset, const char *szName, bool fOwnPal, bool fNoErrIfNotFound, int iFlags)
51 {
52 	// Entry name
53 	char szFilename[_MAX_FNAME+1];
54 	SCopy(szName,szFilename,_MAX_FNAME);
55 	char *szExt = GetExtension(szFilename);
56 	C4Group * pGroup;
57 	if (!*szExt)
58 	{
59 		// no extension: Default to extension that is found as file in group
60 		const char * const extensions[] = { "png", "bmp", "jpeg", "jpg", nullptr };
61 		int i = 0; const char *szExt;
62 		while ((szExt = extensions[i++]))
63 		{
64 			EnforceExtension(szFilename, szExt);
65 			pGroup = hGroupset.FindEntry(szFilename);
66 			if (pGroup) break;
67 		}
68 	}
69 	else
70 		pGroup = hGroupset.FindEntry(szFilename);
71 	if (!pGroup) return false;
72 	// Load surface
73 	return Load(*pGroup,szFilename,fOwnPal,fNoErrIfNotFound,iFlags);
74 }
75 
Load(C4Group & hGroup,const char * szFilename,bool,bool fNoErrIfNotFound,int iFlags)76 bool C4Surface::Load(C4Group &hGroup, const char *szFilename, bool, bool fNoErrIfNotFound, int iFlags)
77 {
78 	int ScaleToSet = 1;
79 	// Image is scaled?
80 	StdStrBuf strFilename;
81 	char strBasename[_MAX_FNAME + 1]; SCopy(szFilename, strBasename, _MAX_FNAME); RemoveExtension(strBasename);
82 	int32_t extpos; int scale;
83 	if (((extpos = SCharLastPos('.', strBasename)) > -1) && (sscanf(strBasename+extpos+1, "%d", &scale) == 1))
84 	{
85 		// Filename with scale information was passed. Just store scale.
86 		ScaleToSet = scale;
87 	}
88 	else
89 	{
90 		// a filename with out scale information was passed in
91 		// Look for scaled images
92 		const size_t base_length = std::strlen(strBasename);
93 		char strExtension[128 + 1]; SCopy(GetExtension(szFilename), strExtension, 128);
94 		if (strExtension[0])
95 		{
96 			char scaled_name[_MAX_PATH+1];
97 			std::string wildcard(strBasename);
98 			wildcard += ".*.";
99 			wildcard += strExtension;
100 			int max_scale = -1;
101 			if (hGroup.FindEntry(wildcard.c_str(), scaled_name))
102 			{
103 				do
104 				{
105 					int scale = -1;
106 					if (sscanf(scaled_name + base_length + 1, "%d", &scale) == 1)
107 						if (scale > max_scale)
108 						{
109 							max_scale = scale;
110 							ScaleToSet = max_scale;
111 							strFilename.Copy(scaled_name);
112 							szFilename = strFilename.getData();
113 						}
114 				}
115 				while (hGroup.FindNextEntry(wildcard.c_str(), scaled_name));
116 			}
117 		}
118 	}
119 	// Access entry
120 	if (!hGroup.AccessEntry(szFilename))
121 	{
122 		// file not found
123 		if (!fNoErrIfNotFound) LogF("%s: %s%c%s", LoadResStr("IDS_PRC_FILENOTFOUND"), hGroup.GetFullName().getData(), (char) DirectorySeparator, szFilename);
124 		return false;
125 	}
126 	bool fSuccess = Read(hGroup, GetExtension(szFilename), iFlags);
127 	// loading error? log!
128 	if (!fSuccess)
129 		LogF("%s: %s%c%s", LoadResStr("IDS_ERR_NOFILE"), hGroup.GetFullName().getData(), (char) DirectorySeparator, szFilename);
130 	// Work around the broken Default()-convention
131 	Scale = ScaleToSet;
132 	// done, success
133 	return fSuccess;
134 }
135 
Read(CStdStream & hGroup,const char * extension,int iFlags)136 bool C4Surface::Read(CStdStream &hGroup, const char * extension, int iFlags)
137 {
138 	// determine file type by file extension and load accordingly
139 	if (SEqualNoCase(extension, "png"))
140 		return ReadPNG(hGroup, iFlags);
141 	else if (SEqualNoCase(extension, "jpeg")
142 	         || SEqualNoCase(extension, "jpg"))
143 		return ReadJPEG(hGroup, iFlags);
144 	else if (SEqualNoCase(extension, "bmp"))
145 		return ReadBMP(hGroup, iFlags);
146 	else
147 		return false;
148 }
149 
ReadPNG(CStdStream & hGroup,int iFlags)150 bool C4Surface::ReadPNG(CStdStream &hGroup, int iFlags)
151 {
152 	// create mem block
153 	int iSize=hGroup.AccessedEntrySize();
154 	BYTE *pData=new BYTE[iSize];
155 	// load file into mem
156 	hGroup.Read((void *) pData, iSize);
157 	// load as png file
158 	CPNGFile png;
159 	bool fSuccess=png.Load(pData, iSize);
160 	// free data
161 	delete [] pData;
162 	// abort if loading wasn't successful
163 	if (!fSuccess) return false;
164 	// create surface(s) - do not create an 8bit-buffer!
165 	if (!Create(png.iWdt, png.iHgt, iFlags)) return false;
166 	// lock for writing data
167 	if (!Lock()) return false;
168 	if (!texture)
169 	{
170 		Unlock();
171 		return false;
172 	}
173 	// write pixels
174 	// Get Texture and lock it
175 	if (!texture->Lock()) return false;
176 	int maxX = std::min(Wdt, iTexSize);
177 	int maxY = std::min(Hgt, iTexSize);
178 	for (int iY = 0; iY < maxY; ++iY)
179 	{
180 #ifndef __BIG_ENDIAN__
181 		if (png.iClrType == PNG_COLOR_TYPE_RGB_ALPHA)
182 		{
183 			// Optimize the easy case of a png in the same format as the display
184 			// 32 bit
185 			DWORD *pPix=(DWORD *) (((char *) texture->texLock.pBits.get()) + iY * texture->texLock.Pitch);
186 			memcpy (pPix, png.GetRow(iY), maxX * sizeof(*pPix));
187 			int iX = maxX;
188 			while (iX--) { if (((BYTE *)pPix)[3] == 0x00) *pPix = 0x00000000; ++pPix; }
189 		}
190 		else
191 #endif
192 		{
193 			// Loop through every pixel and convert
194 			for (int iX = 0; iX < maxX; ++iX)
195 			{
196 				uint32_t dwCol = png.GetPix(iX, iY);
197 				// if color is fully transparent, ensure it's black
198 				if (dwCol>>24 == 0x00) dwCol=0x00000000;
199 				// set pix in surface
200 				DWORD *pPix=(DWORD *) (((char *) texture->texLock.pBits.get()) + iY * texture->texLock.Pitch + iX * 4);
201 				*pPix=dwCol;
202 			}
203 		}
204 	}
205 
206 	// unlock
207 	texture->Unlock();
208 	Unlock();
209 	// return if successful
210 	return fSuccess;
211 }
212 
SavePNG(C4Group & hGroup,const char * szFilename,bool fSaveAlpha,bool fSaveOverlayOnly)213 bool C4Surface::SavePNG(C4Group &hGroup, const char *szFilename, bool fSaveAlpha, bool fSaveOverlayOnly)
214 {
215 	// Using temporary file at C4Group temp path
216 	char szTemp[_MAX_PATH+1];
217 	SCopy(C4Group_GetTempPath(),szTemp);
218 	SAppend(GetFilename(szFilename),szTemp);
219 	MakeTempFilename(szTemp);
220 	// Save to temporary file
221 	if (!C4Surface::SavePNG(szTemp, fSaveAlpha, fSaveOverlayOnly, false)) return false;
222 	// Move temp file to group
223 	if (!hGroup.Move(szTemp,GetFilename(szFilename))) return false;
224 	// Success
225 	return true;
226 }
227 
228 /* JPEG loading */
229 
230 // Some distributions ship jpeglib.h with extern "C", others don't - gah.
231 extern "C"
232 {
233 /* avoid conflict with conflicting FAR typedefs */
234 #undef FAR
235 #include <jpeglib.h>
236 }
237 #include <csetjmp>
238 
239 // Straight from the libjpeg example
240 struct my_error_mgr
241 {
242 	struct jpeg_error_mgr pub;  /* "public" fields */
243 	jmp_buf setjmp_buffer;  /* for return to caller */
244 };
245 
246 typedef struct my_error_mgr * my_error_ptr;
247 
my_error_exit(j_common_ptr cinfo)248 static void my_error_exit (j_common_ptr cinfo)
249 {
250 	/* cinfo->err really points to a my_error_mgr struct, so coerce pointer */
251 	my_error_ptr myerr = (my_error_ptr) cinfo->err;
252 	/* Always display the message. */
253 	/* We could postpone this until after returning, if we chose. */
254 	(*cinfo->err->output_message) (cinfo);
255 	/* Return control to the setjmp point */
256 	longjmp(myerr->setjmp_buffer, 1);
257 }
my_output_message(j_common_ptr cinfo)258 static void my_output_message (j_common_ptr cinfo)
259 {
260 	char buffer[JMSG_LENGTH_MAX];
261 	(*cinfo->err->format_message) (cinfo, buffer);
262 	LogF("libjpeg: %s", buffer);
263 }
jpeg_noop(j_decompress_ptr cinfo)264 static void jpeg_noop (j_decompress_ptr cinfo) {}
265 static const unsigned char end_of_input = JPEG_EOI;
fill_input_buffer(j_decompress_ptr cinfo)266 static boolean fill_input_buffer (j_decompress_ptr cinfo)
267 {
268 	// The doc says to give fake end-of-inputs if there is no more data
269 	cinfo->src->next_input_byte = &end_of_input;
270 	cinfo->src->bytes_in_buffer = 1;
271 	return (boolean)true;
272 }
skip_input_data(j_decompress_ptr cinfo,long num_bytes)273 static void skip_input_data (j_decompress_ptr cinfo, long num_bytes)
274 {
275 	cinfo->src->next_input_byte += num_bytes;
276 	cinfo->src->bytes_in_buffer -= num_bytes;
277 	if (cinfo->src->bytes_in_buffer <= 0)
278 	{
279 		cinfo->src->next_input_byte = &end_of_input;
280 		cinfo->src->bytes_in_buffer = 1;
281 	}
282 }
283 
ReadJPEG(CStdStream & hGroup,int iFlags)284 bool C4Surface::ReadJPEG(CStdStream &hGroup, int iFlags)
285 {
286 	// create mem block
287 	size_t size=hGroup.AccessedEntrySize();
288 	unsigned char *pData=new unsigned char[size];
289 	// load file into mem
290 	hGroup.Read(pData, size);
291 	// stuff for libjpeg
292 	struct jpeg_decompress_struct cinfo;
293 	struct my_error_mgr jerr;
294 	JSAMPARRAY buffer;    /* Output row buffer */
295 	int row_stride;   /* physical row width in output buffer */
296 	/* We set up the normal JPEG error routines, then override error_exit. */
297 	cinfo.err = jpeg_std_error(&jerr.pub);
298 	jerr.pub.error_exit = my_error_exit;
299 	jerr.pub.output_message = my_output_message;
300 	// apparantly, this is needed so libjpeg does not exit() the engine away
301 	if (setjmp(jerr.setjmp_buffer))
302 	{
303 		// some fatal error
304 		jpeg_destroy_decompress(&cinfo);
305 		delete [] pData;
306 		return false;
307 	}
308 	jpeg_create_decompress(&cinfo);
309 
310 	// no fancy function calling
311 	jpeg_source_mgr blub;
312 	cinfo.src = &blub;
313 	blub.next_input_byte = pData;
314 	blub.bytes_in_buffer = size;
315 	blub.init_source = jpeg_noop;
316 	blub.fill_input_buffer = fill_input_buffer;
317 	blub.skip_input_data = skip_input_data;
318 	blub.resync_to_restart = jpeg_resync_to_restart;
319 	blub.term_source = jpeg_noop;
320 
321 	// a missing image is an error
322 	jpeg_read_header(&cinfo, (boolean)true);
323 
324 	// Let libjpeg convert for us
325 	cinfo.out_color_space = JCS_RGB;
326 	jpeg_start_decompress(&cinfo);
327 
328 	// create surface(s) - do not create an 8bit-buffer!
329 	if (!Create(cinfo.output_width, cinfo.output_height, iFlags)) return false;
330 	// JSAMPLEs per row in output buffer
331 	row_stride = cinfo.output_width * cinfo.output_components;
332 	// Make a one-row-high sample array that will go away at jpeg_destroy_decompress
333 	buffer = (*cinfo.mem->alloc_sarray)
334 	         ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
335 	// lock for writing data
336 	if (!Lock()) return false;
337 	while (cinfo.output_scanline < cinfo.output_height)
338 	{
339 		// read an 1-row-array of scanlines
340 		jpeg_read_scanlines(&cinfo, buffer, 1);
341 		// put the data in the image
342 		for (unsigned int i = 0; i < cinfo.output_width; ++i)
343 		{
344 			const unsigned char * const start = buffer[0] + i * cinfo.output_components;
345 			const uint32_t c = C4RGB(*start, *(start + 1), *(start + 2));
346 			SetPixDw(i, cinfo.output_scanline - 1, c);
347 		}
348 	}
349 	// unlock
350 	Unlock();
351 	// clean up
352 	jpeg_finish_decompress(&cinfo);
353 	jpeg_destroy_decompress(&cinfo);
354 	// free data
355 	delete [] pData;
356 	// return if successful
357 	return true;
358 }
359