Added aqua_speed for rite geo 50 tryker
[ryzomcore.git] / nel / tools / 3d / tga_2_dds / tga2dds.cpp
blob48c10452f08153a753593bb77cdcb1a35aaa1722
1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2010-2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
6 //
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
21 #include <iostream>
23 #include "nel/misc/file.h"
24 #include "nel/misc/common.h"
25 #include "nel/misc/bitmap.h"
26 #include "nel/misc/path.h"
27 #include "nel/misc/debug.h"
28 #include "nel/misc/cmd_args.h"
30 #include <math.h>
32 #include "../s3tc_compressor_lib/s3tc_compressor.h"
35 using namespace NLMISC;
36 using namespace std;
38 #ifdef DEBUG_NEW
39 #define new DEBUG_NEW
40 #endif
42 #define TGA8 8
43 #define TGA16 16
44 #define PNG8 108
45 #define PNG16 116
46 #define NOT_DEFINED 0xff
49 bool sameType(const std::string &sFileNameDest, uint8 algo);
50 bool dataCheck(const std::string &sFileNameSrc, const std::string &FileNameDest, uint8 algo);
51 std::string getOutputFileName(const std::string &inputFileName);
56 uint8 getType(const std::string &sFileNameDest)
58 uint32 dds;
59 FILE *f = nlfopen(sFileNameDest, "rb");
60 if(f==NULL)
62 return NOT_DEFINED;
64 CS3TCCompressor::DDS_HEADER h;
66 if (fread(&dds,1,4,f) != 4)
68 fclose(f);
69 return NOT_DEFINED;
72 #ifdef NL_BIG_ENDIAN
73 NLMISC_BSWAP32(dds);
74 #endif
76 if (fread(&h,sizeof(CS3TCCompressor::DDS_HEADER),1,f) != 1)
78 fclose(f);
79 return NOT_DEFINED;
82 if(fclose(f))
84 cerr<<sFileNameDest<< "is not closed"<<endl;
87 if(h.ddpf.dwFourCC==MAKEFOURCC('D','X', 'T', '1')
88 && h.ddpf.dwRGBBitCount==0)
90 return DXT1;
93 if(h.ddpf.dwFourCC==MAKEFOURCC('D','X', 'T', '1')
94 && h.ddpf.dwRGBBitCount>0)
96 return DXT1A;
99 if(h.ddpf.dwFourCC==MAKEFOURCC('D','X', 'T', '3'))
101 return DXT3;
104 if(h.ddpf.dwFourCC==MAKEFOURCC('D','X', 'T', '5'))
106 return DXT5;
109 return NOT_DEFINED;
112 bool sameType(const std::string &sFileNameDest, uint8 &algo, bool wantMipMap)
114 uint32 dds;
115 FILE *f = nlfopen(sFileNameDest, "rb");
116 if(f==NULL)
118 return false;
121 CS3TCCompressor::DDS_HEADER h;
123 if (fread(&dds,1,4,f) != 4)
125 fclose(f);
126 return false;
129 if (fread(&h,sizeof(::DDS_HEADER),1,f) != 1)
131 fclose(f);
132 return false;
135 if(fclose(f))
137 cerr<<sFileNameDest<< "is not closed"<<endl;
140 bool algoOk= false;
141 switch(algo)
143 case DXT1:
144 if(h.ddpf.dwFourCC==MAKEFOURCC('D','X', 'T', '1')
145 && h.ddpf.dwRGBBitCount==0)
146 algoOk=true;
147 break;
149 case DXT1A:
150 if(h.ddpf.dwFourCC==MAKEFOURCC('D','X', 'T', '1')
151 && h.ddpf.dwRGBBitCount>0)
152 algoOk=true;
153 break;
155 case DXT3:
156 if(h.ddpf.dwFourCC==MAKEFOURCC('D','X', 'T', '3'))
157 algoOk=true;
158 break;
160 case DXT5:
161 if(h.ddpf.dwFourCC==MAKEFOURCC('D','X', 'T', '5'))
162 algoOk=true;
163 break;
165 if(!algoOk)
166 return false;
168 // Test Mipmap.
169 bool fileHasMipMap= (h.dwFlags&DDSD_MIPMAPCOUNT) && (h.dwMipMapCount>1);
170 if(fileHasMipMap==wantMipMap)
171 return true;
173 return false;
178 bool dataCheck(const std::string &sFileNameSrc, const std::string &sFileNameDest, uint8& algo, bool wantMipMap)
180 if (!CFile::fileExists(sFileNameSrc))
182 cerr << "Can't open file " << sFileNameSrc << endl;
183 return false;
186 if (!CFile::fileExists(sFileNameDest))
188 return false; // destination file doesn't exist yet
191 uint32 lastWriteTime1 = CFile::getFileModificationDate(sFileNameSrc);
192 uint32 lastWriteTime2 = CFile::getFileModificationDate(sFileNameDest);
194 if(lastWriteTime1 > lastWriteTime2)
196 return false;
198 if (lastWriteTime1 < lastWriteTime2)
200 if(!sameType(sFileNameDest, algo, wantMipMap))
202 return false; // file exists but a new compression type is required
205 return true;
208 std::string getOutputFileName(const std::string &inputFileName)
210 std::string::size_type pos = inputFileName.rfind(".");
211 if(pos == std::string::npos)
213 // name whithout extension
214 return inputFileName + ".dds";
216 else
218 return inputFileName.substr(0,pos) + ".dds";
222 // ***************************************************************************
223 void dividSize (CBitmap &bitmap)
225 // Must be RGBA
226 nlassert (bitmap.getPixelFormat () == CBitmap::RGBA);
228 // Copy the bitmap
229 CBitmap temp = bitmap;
231 // Resize the destination
232 const uint width = temp.getWidth ();
233 const uint height = temp.getHeight ();
234 const uint newWidth = temp.getWidth ()/2;
235 const uint newHeight = temp.getHeight ()/2;
236 bitmap.resize (newWidth, newHeight, CBitmap::RGBA);
238 // Pointers
239 uint8 *pixelSrc = &(temp.getPixels ()[0]);
240 uint8 *pixelDest = &(bitmap.getPixels ()[0]);
242 // Resample
243 uint x, y;
244 for (y=0; y<newHeight; y++)
245 for (x=0; x<newWidth; x++)
247 const uint offsetSrc = ((y*2)*width+x*2)*4;
248 const uint offsetDest = (y*newWidth+x)*4;
249 uint i;
250 for (i=0; i<4; i++)
252 pixelDest[offsetDest+i] = ((uint)pixelSrc[offsetSrc+i] + (uint)pixelSrc[offsetSrc+4+i] +
253 (uint)pixelSrc[offsetSrc+4*width+i] + (uint)pixelSrc[offsetSrc+4*width+4+i])>>2;
258 const int bayerDiv8R[4][4] = {
259 { 7, 3, 6, 2 },
260 { 1, 5, 0, 4 },
261 { 6, 2, 7, 3 },
262 { 0, 4, 1, 5 }
265 const int bayerDiv8G[4][4] = {
266 { 0, 4, 1, 5 },
267 { 6, 2, 7, 3 },
268 { 1, 5, 0, 4 },
269 { 7, 3, 6, 2 }
272 const int bayerDiv8B[4][4] = {
273 { 5, 1, 4, 0 },
274 { 3, 7, 2, 6 },
275 { 4, 0, 5, 1 },
276 { 2, 6, 3, 7 }
279 // ***************************************************************************
280 int main(int argc, char **argv)
282 CApplicationContext applicationContext;
284 // Parse Command Line.
285 //====================
286 NLMISC::CCmdArgs args;
288 args.setDescription(
289 "Convert TGA or PNG image file to DDS compressed file using DXTC compression (DXTC1, DXTC1 with alpha, DXTC3, or DXTC5).\n"
290 " The program looks for possible user color files and load them automatically, a user color file must have the same name that the original tga file, plus the extension \"_usercolor\"\n"
291 "Eg.: pic.tga, the associated user color file must be: pic_usercolor.tga\n"
293 args.addArg("o", "output", "output.dds", "Output DDS filename or directory");
294 args.addArg("a", "algo", "algo", "Conversion algorithm to use\n"
295 " 1 for DXTC1 (no alpha)\n"
296 " 1A for DXTC1 with alpha\n"
297 " 3 for DXTC3\n"
298 " 5 for DXTC5\n"
299 " tga16 for 16 bits TGA\n"
300 " tga8 for 8 bits TGA\n"
301 " png16 for 16 bits PNG\n"
302 " png8 for 8 bits PNG\n"
303 "\n"
304 " default : DXTC1 if 24 bits, DXTC5 if 32 bits."
306 args.addArg("g", "grayscale", "", "Don't load grayscape images as alpha but as grayscale");
307 args.addArg("m", "mipmap", "", "Create MipMap");
308 args.addArg("r", "reduce", "FACTOR", "Reduce the bitmap size before compressing\n FACTOR is 0, 1, 2, 3, 4, 5, 6, 7 or 8");
309 args.addAdditionalArg("input", "PNG or TGA files to convert", false);
311 if (!args.parse(argc, argv)) return 1;
313 string OptOutputFileName;
314 uint8 OptAlgo = NOT_DEFINED;
315 bool OptMipMap = false;
316 bool OptGrayscale = false;
317 uint Reduce = 0;
319 if (args.haveArg("o"))
320 OptOutputFileName = args.getArg("o").front();
322 if (args.haveArg("m"))
323 OptMipMap = true;
325 if (args.haveArg("g"))
326 OptGrayscale = true;
328 if (args.haveArg("a"))
330 std::string strAlgo = toLowerAscii(args.getArg("a").front());
332 if (strAlgo == "1") OptAlgo = DXT1;
333 else if (strAlgo == "1a") OptAlgo = DXT1A;
334 else if (strAlgo == "3") OptAlgo = DXT3;
335 else if (strAlgo == "5") OptAlgo = DXT5;
336 else if (strAlgo == "tga8") OptAlgo = TGA8;
337 else if (strAlgo == "tga16") OptAlgo = TGA16;
338 else if (strAlgo == "png8") OptAlgo = PNG8;
339 else if (strAlgo == "png16") OptAlgo = PNG16;
340 else
342 cerr << "Unknown algorithm: " << strAlgo << endl;
343 return 1;
347 if (args.haveArg("r"))
349 std::string strReduce = args.getArg("r").front();
351 // Reduce size of the bitmap
352 if (fromString(strReduce, Reduce))
354 if (Reduce > 8) Reduce = 8;
358 std::vector<std::string> inputFileNames = args.getAdditionalArg("input");
360 for(uint i = 0; i < inputFileNames.size(); ++i)
362 uint8 algo;
364 // Reading TGA or PNG and converting to RGBA
365 //====================================
366 CBitmap picTga;
367 CBitmap picTga2;
368 CBitmap picSrc;
370 std::string inputFileName = inputFileNames[i];
372 if(inputFileName.find("_usercolor")<inputFileName.length())
374 return 0;
377 NLMISC::CIFile input;
378 if(!input.open(inputFileName))
380 cerr<<"Can't open input file " << inputFileName << endl;
381 return 1;
384 // allow to load an image as grayscale instead of alpha
385 if (OptGrayscale) picTga.loadGrayscaleAsAlpha(false);
387 uint8 imageDepth = picTga.load(input);
388 if(imageDepth==0)
390 cerr<<"Can't load file: "<<inputFileName<<endl;
391 return 1;
393 if(imageDepth!=16 && imageDepth!=24 && imageDepth!=32 && imageDepth!=8)
395 cerr<<"Image not supported: "<<imageDepth<<endl;
396 return 1;
398 input.close();
399 uint32 height = picTga.getHeight();
400 uint32 width= picTga.getWidth();
401 picTga.convertToType (CBitmap::RGBA);
404 // Output file name and algo.
405 //===========================
406 std::string outputFileName;
408 if (!OptOutputFileName.empty())
410 // if OptOutputFileName is a directory, append the original filename
411 if (CFile::isDirectory(OptOutputFileName))
413 outputFileName = CPath::standardizePath(OptOutputFileName) + CFile::getFilename(getOutputFileName(inputFileName));
415 else
417 outputFileName = OptOutputFileName;
419 if (inputFileNames.size() > 1)
421 cerr<<"WARNING! Several files to convert to the same output filename! Use an output directory instead."<<endl;
422 return 1;
426 else
428 outputFileName = getOutputFileName(inputFileName);
431 // Check dest algo
432 if (OptAlgo==NOT_DEFINED)
433 OptAlgo = getType (outputFileName);
435 // Choose Algo.
436 if(OptAlgo!=NOT_DEFINED)
438 algo= OptAlgo;
440 else
442 // TODO: if alpha channel is 0, use DXTC1a instead DXTC1
443 if(imageDepth==24)
444 algo = DXT1;
445 else
446 algo = DXT5;
449 // Data check
450 //===========
451 if(dataCheck(inputFileName,outputFileName, OptAlgo, OptMipMap))
453 cout<<outputFileName<<" : a recent dds file already exists"<<endl;
454 return 0;
458 // Vectors for RGBA data
459 CObjectVector<uint8> RGBASrc = picTga.getPixels();
460 CObjectVector<uint8> RGBASrc2;
461 CObjectVector<uint8> RGBADest;
462 RGBADest.resize(height*width*4);
463 uint dstRGBADestId= 0;
465 // UserColor
466 //===========
468 // Checking if option "usercolor" has been used
469 std::string userColorFileName;
470 if(argc>4)
472 if(strcmp("-usercolor",argv[4])==0)
474 if(argc!=6)
476 writeInstructions();
477 return;
479 userColorFileName = argv[5];
481 else
483 writeInstructions();
484 return;
488 // Checking if associate usercolor file exists
489 std::string userColorFileName;
490 std::string::size_type pos = inputFileName.rfind(".");
491 if (pos == std::string::npos)
493 // name without extension
494 userColorFileName = inputFileName + "_usercolor";
496 else
498 // append input filename extension
499 userColorFileName = inputFileName.substr(0,pos) + "_usercolor" + inputFileName.substr(pos);
502 // Reading second Tga for user color, don't complain if _usercolor is missing
503 NLMISC::CIFile input2;
504 if (CPath::exists(userColorFileName) && input2.open(userColorFileName))
506 picTga2.load(input2);
507 uint32 height2 = picTga2.getHeight();
508 uint32 width2 = picTga2.getWidth();
509 nlassert(width2==width);
510 nlassert(height2==height);
511 picTga2.convertToType (CBitmap::RGBA);
513 RGBASrc2 = picTga2.getPixels();
515 NLMISC::CRGBA *pRGBASrc = (NLMISC::CRGBA*)&RGBASrc[0];
516 NLMISC::CRGBA *pRGBASrc2 = (NLMISC::CRGBA*)&RGBASrc2[0];
518 for(uint32 i = 0; i<width*height; i++)
520 // If no UserColor, must take same RGB, and keep same Alpha from src1 !!! So texture can have both alpha
521 // userColor and other alpha usage.
522 if(pRGBASrc2[i].A==255)
524 RGBADest[dstRGBADestId++]= pRGBASrc[i].R;
525 RGBADest[dstRGBADestId++]= pRGBASrc[i].G;
526 RGBADest[dstRGBADestId++]= pRGBASrc[i].B;
527 RGBADest[dstRGBADestId++]= pRGBASrc[i].A;
529 else
531 // Old code.
532 /*uint8 F = (uint8) ((float)pRGBASrc[i].R*0.3 + (float)pRGBASrc[i].G*0.56 + (float)pRGBASrc[i].B*0.14);
533 uint8 Frgb;
534 if((F*pRGBASrc2[i].A/255)==255)
535 Frgb = 0;
536 else
537 Frgb = (255-pRGBASrc2[i].A)/(255-F*pRGBASrc2[i].A/255);
538 RGBADest[dstRGBADestId++]= Frgb*pRGBASrc[i].R/255;
539 RGBADest[dstRGBADestId++]= Frgb*pRGBASrc[i].G/255;
540 RGBADest[dstRGBADestId++]= Frgb*pRGBASrc[i].B/255;
541 RGBADest[dstRGBADestId++]= F*pRGBASrc[i].A/255;*/
543 // New code: use new restrictions from IDriver.
544 float Rt, Gt, Bt, At;
545 float Lt;
546 float Rtm, Gtm, Btm, Atm;
548 // read 0-1 RGB pixel.
549 Rt= (float)pRGBASrc[i].R/255;
550 Gt= (float)pRGBASrc[i].G/255;
551 Bt= (float)pRGBASrc[i].B/255;
552 Lt= Rt*0.3f + Gt*0.56f + Bt*0.14f;
554 // take Alpha from userColor src.
555 At= (float)pRGBASrc2[i].A/255;
556 Atm= 1-Lt*(1-At);
558 // If normal case.
559 if(Atm>0)
561 Rtm= Rt*At / Atm;
562 Gtm= Gt*At / Atm;
563 Btm= Bt*At / Atm;
565 // Else special case: At==0, and Lt==1.
566 else
568 Rtm= Gtm= Btm= 0;
571 // copy to buffer.
572 sint r,g,b,a;
573 r= (sint)floor(Rtm*255+0.5f);
574 g= (sint)floor(Gtm*255+0.5f);
575 b= (sint)floor(Btm*255+0.5f);
576 a= (sint)floor(Atm*255+0.5f);
577 clamp(r, 0,255);
578 clamp(g, 0,255);
579 clamp(b, 0,255);
580 clamp(a, 0,255);
581 RGBADest[dstRGBADestId++]= r;
582 RGBADest[dstRGBADestId++]= g;
583 RGBADest[dstRGBADestId++]= b;
584 RGBADest[dstRGBADestId++]= a;
588 else
589 RGBADest = RGBASrc;
591 // Copy to the dest bitmap.
592 picSrc.resize(width, height, CBitmap::RGBA);
593 picSrc.getPixels(0)= RGBADest;
595 // Resize the destination bitmap ?
596 while (Reduce != 0)
598 dividSize (picSrc);
599 Reduce--;
602 if (algo == TGA16)
604 // Apply bayer dither
605 CObjectVector<uint8> &rgba = picSrc.getPixels(0);
606 const uint32 w = picSrc.getWidth(0);
607 uint32 x = 0;
608 uint32 y = 0;
609 for (uint32 i = 0; i < rgba.size(); i += 4)
611 NLMISC::CRGBA &c = reinterpret_cast<NLMISC::CRGBA &>(rgba[i]);
612 c.R = (uint8)std::min(255, (int)c.R + bayerDiv8R[x % 4][y % 4]);
613 c.G = (uint8)std::min(255, (int)c.G + bayerDiv8G[x % 4][y % 4]);
614 c.B = (uint8)std::min(255, (int)c.B + bayerDiv8B[x % 4][y % 4]);
615 ++x;
616 x %= w;
617 if (x == 0)
618 ++y;
622 // 8 or 16 bits TGA or PNG ?
623 if ((algo == TGA16) || (algo == TGA8) || (algo == PNG16) || (algo == PNG8))
625 // Saving TGA or PNG file
626 //=================
627 NLMISC::COFile output;
628 if(!output.open(outputFileName))
630 cerr<<"Can't open output file "<<outputFileName<<endl;
631 return 1;
635 if (algo == TGA16)
637 picSrc.writeTGA (output, 16);
639 else if (algo == TGA8)
641 picSrc.convertToType(CBitmap::Luminance);
642 picSrc.writeTGA (output, 8);
644 else if (algo == PNG16)
646 picSrc.writePNG (output, 16);
648 else if (algo == PNG8)
650 picSrc.convertToType(CBitmap::Luminance);
651 picSrc.writePNG (output, 8);
654 catch(const NLMISC::EWriteError &e)
656 cerr<<e.what()<<endl;
657 return 1;
660 output.close();
662 else
664 // Compress
665 //===========
667 // log.
668 std::string algostr;
669 switch(algo)
671 case DXT1:
672 algostr = "DXTC1";
673 break;
674 case DXT1A:
675 algostr = "DXTC1A";
676 break;
677 case DXT3:
678 algostr = "DXTC3";
679 break;
680 case DXT5:
681 algostr = "DXTC5";
682 break;
684 cout<<"compressing ("<<algostr<<") "<<inputFileName<<" to "<<outputFileName<<endl;
687 // Saving compressed DDS file
688 // =================
689 NLMISC::COFile output;
690 if(!output.open(outputFileName))
692 cerr<<"Can't open output file "<<outputFileName<<endl;
693 return 1;
697 CS3TCCompressor comp;
698 comp.compress(picSrc, OptMipMap, algo, output);
700 catch(const NLMISC::EWriteError &e)
702 cerr<<e.what()<<endl;
703 return 1;
706 output.close();
710 return 0;