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