Merge pull request #4927 from mkortstiege/masterprompt
[vuplus_xbmc] / tools / TexturePacker / XBMCTex.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #ifdef TARGET_WINDOWS
22 #include <sys/types.h>
23 #include <sys/stat.h>
24 #define __STDC_FORMAT_MACROS
25 #include <inttypes.h>
26 #endif
27 //#include <string>
28 #include <cerrno>
29 //#include <cstring>
30 #include <dirent.h>
31 #include <map>
32
33 #include <SDL/SDL.h>
34 #include <SDL/SDL_image.h>
35 #undef main
36
37 #include "guilib/XBTF.h"
38 #include "XBTFWriter.h"
39 #include "md5.h"
40 #include "SDL_anigif.h"
41 #include "cmdlineargs.h"
42 #include "libsquish/squish.h"
43
44 #ifdef TARGET_WINDOWS
45 #define strncasecmp strnicmp
46 #endif
47
48 #ifdef USE_LZO_PACKING
49 #ifdef TARGET_WINDOWS
50 #include "../../lib/win32/liblzo/LZO1X.H"
51 #else
52 #include <lzo/lzo1x.h>
53 #endif
54 #endif
55
56 using namespace std;
57
58 #define FLAGS_USE_LZO     1
59 #define FLAGS_ALLOW_YCOCG 2
60 #define FLAGS_USE_DXT     4
61
62 #define DIR_SEPARATOR "/"
63 #define DIR_SEPARATOR_CHAR '/'
64
65 int NP2( unsigned x )
66 {
67   --x;
68   x |= x >> 1;
69   x |= x >> 2;
70   x |= x >> 4;
71   x |= x >> 8;
72   x |= x >> 16;
73   return ++x;
74 }
75
76 const char *GetFormatString(unsigned int format)
77 {
78   switch (format)
79   {
80   case XB_FMT_DXT1:
81     return "DXT1 ";
82   case XB_FMT_DXT3:
83     return "DXT3 ";
84   case XB_FMT_DXT5:
85     return "DXT5 ";
86   case XB_FMT_DXT5_YCoCg:
87     return "YCoCg";
88   case XB_FMT_A8R8G8B8:
89     return "ARGB ";
90   case XB_FMT_A8:
91     return "A8   ";
92   default:
93     return "?????";
94   }
95 }
96
97 // returns true for png, bmp, tga, jpg and dds files, otherwise returns false
98 bool IsGraphicsFile(char *strFileName)
99 {
100   size_t n = strlen(strFileName);
101   if (n < 4)
102     return false;
103
104   if (strncasecmp(&strFileName[n-4], ".png", 4) &&
105       strncasecmp(&strFileName[n-4], ".bmp", 4) &&
106       strncasecmp(&strFileName[n-4], ".tga", 4) &&
107       strncasecmp(&strFileName[n-4], ".gif", 4) &&
108       strncasecmp(&strFileName[n-4], ".tbn", 4) &&
109       strncasecmp(&strFileName[n-4], ".jpg", 4))
110     return false;
111
112   return true;
113 }
114
115 // returns true for png, bmp, tga, jpg and dds files, otherwise returns false
116 bool IsGIF(const char *strFileName)
117 {
118   size_t n = strlen(strFileName);
119   if (n < 4)
120     return false;
121
122   if (strncasecmp(&strFileName[n-4], ".gif", 4))
123     return false;
124
125   return true;
126 }
127
128 void CreateSkeletonHeaderImpl(CXBTF& xbtf, std::string fullPath, std::string relativePath)
129 {
130   struct dirent* dp;
131   struct stat stat_p;
132   DIR *dirp = opendir(fullPath.c_str());
133
134   if (dirp)
135   {
136     while ((dp = readdir(dirp)) != NULL)
137     {
138       if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) 
139       {
140         continue;
141       }
142
143       //stat to check for dir type (reiserfs fix)
144       std::string fileN = fullPath + "/" + dp->d_name;
145       if (stat(fileN.c_str(), &stat_p) == 0)
146       {
147         if (dp->d_type == DT_DIR || stat_p.st_mode & S_IFDIR)
148         {
149           std::string tmpPath = relativePath;
150           if (tmpPath.size() > 0)
151           {
152             tmpPath += "/";
153           }
154
155           CreateSkeletonHeaderImpl(xbtf, fullPath + DIR_SEPARATOR + dp->d_name, tmpPath + dp->d_name);
156         }
157         else if (IsGraphicsFile(dp->d_name))
158         {
159           std::string fileName = "";
160           if (relativePath.size() > 0)
161           {
162             fileName += relativePath;
163             fileName += "/";
164           }
165
166           fileName += dp->d_name;
167
168           CXBTFFile file;
169           file.SetPath(fileName);
170           xbtf.GetFiles().push_back(file);
171         }
172       }
173     }
174
175     closedir(dirp);
176   }
177   else
178   {
179     printf("Error opening %s (%s)\n", fullPath.c_str(), strerror(errno));
180   }
181 }
182
183 void CreateSkeletonHeader(CXBTF& xbtf, std::string fullPath)
184 {
185   std::string temp;
186   CreateSkeletonHeaderImpl(xbtf, fullPath, temp);
187 }
188
189 CXBTFFrame appendContent(CXBTFWriter &writer, int width, int height, unsigned char *data, unsigned int size, unsigned int format, bool hasAlpha, unsigned int flags)
190 {
191   CXBTFFrame frame;
192 #ifdef USE_LZO_PACKING
193   lzo_uint packedSize = size;
194
195   if ((flags & FLAGS_USE_LZO) == FLAGS_USE_LZO)
196   {
197     // grab a temporary buffer for unpacking into
198     packedSize = size + size / 16 + 64 + 3; // see simple.c in lzo
199     unsigned char *packed  = new unsigned char[packedSize];
200     unsigned char *working = new unsigned char[LZO1X_999_MEM_COMPRESS];
201     if (packed && working)
202     {
203       if (lzo1x_999_compress(data, size, packed, &packedSize, working) != LZO_E_OK || packedSize > size)
204       {
205         // compression failed, or compressed size is bigger than uncompressed, so store as uncompressed
206         packedSize = size;
207         writer.AppendContent(data, size);
208       }
209       else
210       { // success
211         lzo_uint optimSize = size;
212         if (lzo1x_optimize(packed, packedSize, data, &optimSize, NULL) != LZO_E_OK || optimSize != size)
213         { //optimisation failed
214           packedSize = size;
215           writer.AppendContent(data, size);
216         }
217         else
218         { // success
219           writer.AppendContent(packed, packedSize);
220         }
221       }
222       delete[] working;
223       delete[] packed;
224     }
225   }
226   else
227 #else
228   unsigned int packedSize = size;
229 #endif
230   {
231     writer.AppendContent(data, size);
232   }
233   frame.SetPackedSize(packedSize);
234   frame.SetUnpackedSize(size);
235   frame.SetWidth(width);
236   frame.SetHeight(height);
237   frame.SetFormat(hasAlpha ? format : format | XB_FMT_OPAQUE);
238   frame.SetDuration(0);
239   return frame;
240 }
241
242 void CompressImage(const squish::u8 *brga, int width, int height, squish::u8 *compressed, unsigned int flags, double &colorMSE, double &alphaMSE)
243 {
244   squish::CompressImage(brga, width, height, compressed, flags | squish::kSourceBGRA);
245   squish::ComputeMSE(brga, width, height, compressed, flags | squish::kSourceBGRA, colorMSE, alphaMSE);
246 }
247
248 bool HasAlpha(unsigned char *argb, unsigned int width, unsigned int height)
249 {
250   unsigned char *p = argb + 3; // offset of alpha
251   for (unsigned int i = 0; i < 4*width*height; i += 4)
252   {
253     if (p[i] != 0xff)
254       return true;
255   }
256   return false;
257 }
258
259 CXBTFFrame createXBTFFrame(SDL_Surface* image, CXBTFWriter& writer, double maxMSE, unsigned int flags)
260 {
261   // Convert to ARGB
262   SDL_PixelFormat argbFormat;
263   memset(&argbFormat, 0, sizeof(SDL_PixelFormat));
264   argbFormat.BitsPerPixel = 32;
265   argbFormat.BytesPerPixel = 4;
266
267   // For DXT5 we need RGBA
268 #if defined(HOST_BIGENDIAN)
269   argbFormat.Amask = 0x000000ff;
270   argbFormat.Ashift = 0;
271   argbFormat.Rmask = 0x0000ff00;
272   argbFormat.Rshift = 8;
273   argbFormat.Gmask = 0x00ff0000;
274   argbFormat.Gshift = 16;
275   argbFormat.Bmask = 0xff000000;
276   argbFormat.Bshift = 24;
277 #else
278   argbFormat.Amask = 0xff000000;
279   argbFormat.Ashift = 24;
280   argbFormat.Rmask = 0x00ff0000;
281   argbFormat.Rshift = 16;
282   argbFormat.Gmask = 0x0000ff00;
283   argbFormat.Gshift = 8;
284   argbFormat.Bmask = 0x000000ff;
285   argbFormat.Bshift = 0;
286 #endif
287
288   int width, height;
289   unsigned int format = 0;
290   SDL_Surface *argbImage = SDL_ConvertSurface(image, &argbFormat, 0);
291   unsigned char* argb = (unsigned char*)argbImage->pixels;
292   unsigned int compressedSize = 0;
293   unsigned char* compressed = NULL;
294   
295   width  = image->w;
296   height = image->h;
297   bool hasAlpha = HasAlpha(argb, width, height);
298   
299   if (flags & FLAGS_USE_DXT)
300   {
301     double colorMSE, alphaMSE;
302     compressedSize = squish::GetStorageRequirements(width, height, squish::kDxt5);
303     compressed = new unsigned char[compressedSize];
304     // first try DXT1, which is only 4bits/pixel
305     CompressImage(argb, width, height, compressed, squish::kDxt1, colorMSE, alphaMSE);
306     if (colorMSE < maxMSE && alphaMSE < maxMSE)
307     { // success - use it
308       compressedSize = squish::GetStorageRequirements(width, height, squish::kDxt1);
309       format = XB_FMT_DXT1;
310     }
311     /* 
312     if (!format && alphaMSE == 0 && (flags & FLAGS_ALLOW_YCOCG) == FLAGS_ALLOW_YCOCG)
313     { 
314       // no alpha channel, so DXT5YCoCg is going to be the best DXT5 format
315       CompressImage(argb, width, height, compressed, squish::kDxt5 | squish::kUseYCoCg, colorMSE, alphaMSE);
316       if (colorMSE < maxMSE && alphaMSE < maxMSE)
317       { // success - use it
318         compressedSize = squish::GetStorageRequirements(width, height, squish::kDxt5);
319         format = XB_FMT_DXT5_YCoCg;
320       }
321     }
322     */
323     if (!format)
324     { // try DXT3 and DXT5 - use whichever is better (color is the same, but alpha will be different)
325       CompressImage(argb, width, height, compressed, squish::kDxt3, colorMSE, alphaMSE);
326       if (colorMSE < maxMSE)
327       { // color is fine, test DXT5 as well
328         double dxt5MSE;
329         squish::u8* compressed2 = new squish::u8[squish::GetStorageRequirements(width, height, squish::kDxt5)];
330         CompressImage(argb, width, height, compressed2, squish::kDxt5, colorMSE, dxt5MSE);
331         if (alphaMSE < maxMSE && alphaMSE < dxt5MSE)
332         { // DXT3 passes and is best
333           compressedSize = squish::GetStorageRequirements(width, height, squish::kDxt3);
334           format = XB_FMT_DXT3;
335         }
336         else if (dxt5MSE < maxMSE)
337         { // DXT5 passes
338           compressedSize = squish::GetStorageRequirements(width, height, squish::kDxt5);
339           memcpy(compressed, compressed2, compressedSize);
340           format = XB_FMT_DXT5;
341         }
342         delete[] compressed2;
343       }
344     }
345   }
346
347   CXBTFFrame frame; 
348   if (format)
349   {
350     frame = appendContent(writer, width, height, compressed, compressedSize, format, hasAlpha, flags);
351     if (compressedSize)
352       delete[] compressed;
353   }
354   else
355   {
356     // none of the compressed stuff works for us, so we use 32bit texture
357     format = XB_FMT_A8R8G8B8;
358     frame = appendContent(writer, width, height, argb, (width * height * 4), format, hasAlpha, flags);
359   }
360
361   SDL_FreeSurface(argbImage);
362   return frame;
363 }
364
365 void Usage()
366 {
367   puts("Usage:");
368   puts("  -help            Show this screen.");
369   puts("  -input <dir>     Input directory. Default: current dir");
370   puts("  -output <dir>    Output directory/filename. Default: Textures.xpr");
371   puts("  -dupecheck       Enable duplicate file detection. Reduces output file size. Default: on");
372   puts("  -use_lzo         Use lz0 packing.     Default: on");
373   puts("  -use_dxt         Use DXT compression. Default: on");
374   puts("  -use_none        Use No  compression. Default: off");
375 }
376
377 static bool checkDupe(struct MD5Context* ctx,
378                       map<string,unsigned int>& hashes,
379                       vector<unsigned int>& dupes, unsigned int pos)
380 {
381   unsigned char digest[17];
382   MD5Final(digest,ctx);
383   digest[16] = 0;
384   char hex[33];
385   sprintf(hex, "%02X%02X%02X%02X%02X%02X%02X%02X"\
386       "%02X%02X%02X%02X%02X%02X%02X%02X", digest[0], digest[1], digest[2],
387       digest[3], digest[4], digest[5], digest[6], digest[7], digest[8],
388       digest[9], digest[10], digest[11], digest[12], digest[13], digest[14],
389       digest[15]);
390   hex[32] = 0;
391   map<string,unsigned int>::iterator it = hashes.find(hex);
392   if (it != hashes.end())
393   {
394     dupes[pos] = it->second; 
395     return true;
396   }
397
398   hashes.insert(make_pair(hex,pos));
399   dupes[pos] = pos;
400
401   return false;
402 }
403
404 int createBundle(const std::string& InputDir, const std::string& OutputFile, double maxMSE, unsigned int flags, bool dupecheck)
405 {
406   map<string,unsigned int> hashes;
407   vector<unsigned int> dupes;
408   CXBTF xbtf;
409   CreateSkeletonHeader(xbtf, InputDir);
410   dupes.resize(xbtf.GetFiles().size());
411   if (!dupecheck)
412   {
413     for (unsigned int i=0;i<dupes.size();++i)
414       dupes[i] = i;
415   }
416
417   CXBTFWriter writer(xbtf, OutputFile);
418   if (!writer.Create())
419   {
420     printf("Error creating file\n");
421     return 1;
422   }
423
424   std::vector<CXBTFFile>& files = xbtf.GetFiles();
425   for (size_t i = 0; i < files.size(); i++)
426   {
427     struct MD5Context ctx;
428     MD5Init(&ctx);
429     CXBTFFile& file = files[i];
430
431     std::string fullPath = InputDir;
432     fullPath += file.GetPath();
433
434     std::string output = file.GetPath();
435     output = output.substr(0, 40);
436     while (output.size() < 46)
437       output += ' ';
438     if (!IsGIF(fullPath.c_str()))
439     {
440       // Load the image
441       SDL_Surface* image = IMG_Load(fullPath.c_str());
442       if (!image)
443       {
444         printf("...unable to load image %s\n", file.GetPath());
445         continue;
446       }
447
448       bool skip=false;
449       printf("%s", output.c_str());
450       if (dupecheck)
451       {
452         MD5Update(&ctx,(const uint8_t*)image->pixels,image->h*image->pitch);
453         if (checkDupe(&ctx,hashes,dupes,i))
454         {
455           printf("****  duplicate of %s\n", files[dupes[i]].GetPath());
456           file.GetFrames().insert(file.GetFrames().end(),
457             files[dupes[i]].GetFrames().begin(), files[dupes[i]].GetFrames().end());
458           skip = true;
459         }
460       }
461
462       if (!skip)
463       {
464         CXBTFFrame frame = createXBTFFrame(image, writer, maxMSE, flags);
465
466         printf("%s%c (%d,%d @ %"PRIu64" bytes)\n", GetFormatString(frame.GetFormat()), frame.HasAlpha() ? ' ' : '*',
467           frame.GetWidth(), frame.GetHeight(), frame.GetUnpackedSize());
468
469         file.SetLoop(0);
470         file.GetFrames().push_back(frame);
471       }
472       SDL_FreeSurface(image);
473     }
474     else
475     {
476       int gnAG = AG_LoadGIF(fullPath.c_str(), NULL, 0);
477       AG_Frame* gpAG = new AG_Frame[gnAG];
478       AG_LoadGIF(fullPath.c_str(), gpAG, gnAG);
479
480       printf("%s\n", output.c_str());
481       bool skip=false;
482       if (dupecheck)
483       {
484         for (int j = 0; j < gnAG; j++)
485           MD5Update(&ctx,
486             (const uint8_t*)gpAG[j].surface->pixels,
487             gpAG[j].surface->h * gpAG[j].surface->pitch);
488
489         if (checkDupe(&ctx,hashes,dupes,i))
490         {
491           printf("****  duplicate of %s\n", files[dupes[i]].GetPath());
492           file.GetFrames().insert(file.GetFrames().end(),
493             files[dupes[i]].GetFrames().begin(), files[dupes[i]].GetFrames().end());
494           skip = true;
495         }
496       }
497
498       if (!skip)
499       {
500         for (int j = 0; j < gnAG; j++)
501         {
502           printf("    frame %4i                                ", j);
503           CXBTFFrame frame = createXBTFFrame(gpAG[j].surface, writer, maxMSE, flags);
504           frame.SetDuration(gpAG[j].delay);
505           file.GetFrames().push_back(frame);
506           printf("%s%c (%d,%d @ %"PRIu64" bytes)\n", GetFormatString(frame.GetFormat()), frame.HasAlpha() ? ' ' : '*',
507             frame.GetWidth(), frame.GetHeight(), frame.GetUnpackedSize());
508         }
509       }
510       AG_FreeSurfaces(gpAG, gnAG);
511       delete [] gpAG;
512
513       file.SetLoop(0);
514     }
515   }
516
517   if (!writer.UpdateHeader(dupes))
518   {
519     printf("Error writing header to file\n");
520     return 1;
521   }
522
523   if (!writer.Close())
524   {
525     printf("Error closing file\n");
526     return 1;
527   }
528
529   return 0;
530 }
531
532 int main(int argc, char* argv[])
533 {
534 #ifdef USE_LZO_PACKING
535   if (lzo_init() != LZO_E_OK)
536     return 1;
537 #endif
538   bool valid = false;
539   unsigned int flags = 0;
540   bool dupecheck = false;
541   CmdLineArgs args(argc, (const char**)argv);
542
543   // setup some defaults, dxt with lzo post packing,
544   flags = FLAGS_USE_DXT;
545 #ifdef USE_LZO_PACKING
546   flags |= FLAGS_USE_LZO;
547 #endif
548
549   if (args.size() == 1)
550   {
551     Usage();
552     return 1;
553   }
554
555   std::string InputDir;
556   std::string OutputFilename = "Textures.xbt";
557
558   for (unsigned int i = 1; i < args.size(); ++i)
559   {
560     if (!stricmp(args[i], "-help") || !stricmp(args[i], "-h") || !stricmp(args[i], "-?"))
561     {
562       Usage();
563       return 1;
564     }
565     else if (!stricmp(args[i], "-input") || !stricmp(args[i], "-i"))
566     {
567       InputDir = args[++i];
568       valid = true;
569     }
570     else if (!strcmp(args[i], "-dupecheck"))
571     {
572       dupecheck = true;
573     }
574     else if (!stricmp(args[i], "-output") || !stricmp(args[i], "-o"))
575     {
576       OutputFilename = args[++i];
577       valid = true;
578 #ifdef TARGET_POSIX
579       char *c = NULL;
580       while ((c = (char *)strchr(OutputFilename.c_str(), '\\')) != NULL) *c = '/';
581 #endif
582     }
583     else if (!stricmp(args[i], "-use_none"))
584     {
585       flags &= ~FLAGS_USE_DXT;
586     }
587     else if (!stricmp(args[i], "-use_dxt"))
588     {
589       flags |= FLAGS_USE_DXT;
590     }
591 #ifdef USE_LZO_PACKING
592     else if (!stricmp(args[i], "-use_lzo"))
593     {
594       flags |= FLAGS_USE_LZO;
595     }
596 #endif
597     else
598     {
599       printf("Unrecognized command line flag: %s\n", args[i]);
600     }
601   }
602
603   if (!valid)
604   {
605     Usage();
606     return 1;
607   }
608
609   size_t pos = InputDir.find_last_of(DIR_SEPARATOR);
610   if (pos != InputDir.length() - 1)
611     InputDir += DIR_SEPARATOR;
612
613   double maxMSE = 1.5;    // HQ only please
614   createBundle(InputDir, OutputFilename, maxMSE, flags, dupecheck);
615 }