OpenClonk
C4SurfaceLoaders.cpp
Go to the documentation of this file.
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 
28 bool C4Surface::LoadAny(C4Group &hGroup, const char *szName, bool fOwnPal, bool fNoErrIfNotFound, int iFlags)
29 {
30  // Entry name
31  char szFilename[_MAX_FNAME_LEN];
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 
50 bool C4Surface::LoadAny(C4GroupSet &hGroupset, const char *szName, bool fOwnPal, bool fNoErrIfNotFound, int iFlags)
51 {
52  // Entry name
53  char szFilename[_MAX_FNAME_LEN];
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 
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_LEN]; 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_LEN];
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 
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 
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 
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_LEN];
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
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 
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 }
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 }
264 static void jpeg_noop (j_decompress_ptr cinfo) {}
265 static const unsigned char end_of_input = JPEG_EOI;
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 }
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 
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 }
const char * C4Group_GetTempPath()
Definition: C4Group.cpp:87
const char * LoadResStr(const char *id)
Definition: C4Language.h:83
bool LogF(const char *strMessage,...)
Definition: C4Log.cpp:262
struct my_error_mgr * my_error_ptr
struct jpeg_error_mgr pub
#define _MAX_FNAME
#define DirectorySeparator
#define _MAX_PATH_LEN
#define _MAX_FNAME_LEN
uint8_t BYTE
uint32_t DWORD
void SCopy(const char *szSource, char *sTarget, size_t iMaxL)
Definition: Standard.cpp:152
bool SEqualNoCase(const char *szStr1, const char *szStr2, int iLen)
Definition: Standard.cpp:213
int SCharLastPos(char cTarget, const char *szInStr)
Definition: Standard.cpp:253
void SAppend(const char *szSource, char *szTarget, int iMaxL)
Definition: Standard.cpp:263
#define C4RGB(r, g, b)
Definition: StdColors.h:26
char * GetExtension(char *szFilename)
Definition: StdFile.cpp:118
void MakeTempFilename(char *szFilename)
Definition: StdFile.cpp:320
char * GetFilename(char *szPath)
Definition: StdFile.cpp:42
void EnforceExtension(char *szFilename, const char *szExtension)
Definition: StdFile.cpp:286
void RemoveExtension(char *szFilename)
Definition: StdFile.cpp:303
int iSize
Definition: TstC4NetIO.cpp:32
bool FindNextEntry(const char *wildcard, StdStrBuf *filename=nullptr, size_t *size=nullptr, bool start_at_filename=false)
Definition: C4Group.cpp:2217
bool AccessEntry(const char *wildcard, size_t *size=nullptr, char *filename=nullptr, bool needs_to_be_a_group=false)
Definition: C4Group.cpp:2104
StdStrBuf GetFullName() const
Definition: C4Group.cpp:2638
bool Move(const char *filename, const char *entry_name)
Definition: C4Group.cpp:1633
bool FindEntry(const char *wildcard, StdStrBuf *filename=nullptr, size_t *size=nullptr)
Definition: C4Group.cpp:2211
C4Group * FindEntry(const char *szWildcard, int32_t *pPriority=nullptr, int32_t *pID=nullptr)
Definition: C4GroupSet.cpp:175
bool SetPixDw(int iX, int iY, DWORD dwCol)
Definition: C4Surface.cpp:576
bool Load(C4Group &hGroup, const char *szFilename, bool fOwnPal, bool fNoErrIfNotFound, int iFlags)
int Wdt
Definition: C4Surface.h:65
int iTexSize
Definition: C4Surface.h:68
int Scale
Definition: C4Surface.h:66
std::unique_ptr< C4TexRef > texture
Definition: C4Surface.h:78
bool LoadAny(C4Group &hGroup, const char *szFilename, bool fOwnPal, bool fNoErrIfNotFound, int iFlags)
bool ReadBMP(CStdStream &hGroup, int iFlags)
Definition: C4Surface.cpp:323
bool Read(CStdStream &hGroup, const char *extension, int iFlags)
bool Create(int iWdt, int iHgt, int iFlags=0)
Definition: C4Surface.cpp:161
bool ReadJPEG(CStdStream &hGroup, int iFlags)
bool Unlock()
Definition: C4Surface.cpp:464
bool ReadPNG(CStdStream &hGroup, int iFlags)
bool Lock()
Definition: C4Surface.cpp:453
bool SavePNG(C4Group &hGroup, const char *szFilename, bool fSaveAlpha=true, bool fSaveOverlayOnly=false)
int Hgt
Definition: C4Surface.h:65
uint32_t * GetRow(int iY)
Definition: StdPNG.h:56
unsigned long iHgt
Definition: StdPNG.h:44
int iClrType
Definition: StdPNG.h:45
bool Load(BYTE *pFile, int iSize)
Definition: StdPNG.cpp:155
unsigned long iWdt
Definition: StdPNG.h:44
DWORD GetPix(int iX, int iY)
Definition: StdPNG.cpp:175
virtual bool Read(void *pBuffer, size_t iSize)=0
virtual size_t AccessedEntrySize() const =0
const char * getData() const
Definition: StdBuf.h:442
void Copy()
Definition: StdBuf.h:467