1 // NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
2 // Copyright (C) 2010 Winch Gate Property Limited
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2010-2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
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/>.
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"
32 #include "../s3tc_compressor_lib/s3tc_compressor.h"
35 using namespace NLMISC
;
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
)
59 FILE *f
= nlfopen(sFileNameDest
, "rb");
64 CS3TCCompressor::DDS_HEADER h
;
66 if (fread(&dds
,1,4,f
) != 4)
76 if (fread(&h
,sizeof(CS3TCCompressor::DDS_HEADER
),1,f
) != 1)
84 cerr
<<sFileNameDest
<< "is not closed"<<endl
;
87 if(h
.ddpf
.dwFourCC
==MAKEFOURCC('D','X', 'T', '1')
88 && h
.ddpf
.dwRGBBitCount
==0)
93 if(h
.ddpf
.dwFourCC
==MAKEFOURCC('D','X', 'T', '1')
94 && h
.ddpf
.dwRGBBitCount
>0)
99 if(h
.ddpf
.dwFourCC
==MAKEFOURCC('D','X', 'T', '3'))
104 if(h
.ddpf
.dwFourCC
==MAKEFOURCC('D','X', 'T', '5'))
112 bool sameType(const std::string
&sFileNameDest
, uint8
&algo
, bool wantMipMap
)
115 FILE *f
= nlfopen(sFileNameDest
, "rb");
121 CS3TCCompressor::DDS_HEADER h
;
123 if (fread(&dds
,1,4,f
) != 4)
129 if (fread(&h
,sizeof(::DDS_HEADER
),1,f
) != 1)
137 cerr
<<sFileNameDest
<< "is not closed"<<endl
;
144 if(h
.ddpf
.dwFourCC
==MAKEFOURCC('D','X', 'T', '1')
145 && h
.ddpf
.dwRGBBitCount
==0)
150 if(h
.ddpf
.dwFourCC
==MAKEFOURCC('D','X', 'T', '1')
151 && h
.ddpf
.dwRGBBitCount
>0)
156 if(h
.ddpf
.dwFourCC
==MAKEFOURCC('D','X', 'T', '3'))
161 if(h
.ddpf
.dwFourCC
==MAKEFOURCC('D','X', 'T', '5'))
169 bool fileHasMipMap
= (h
.dwFlags
&DDSD_MIPMAPCOUNT
) && (h
.dwMipMapCount
>1);
170 if(fileHasMipMap
==wantMipMap
)
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
;
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
)
198 if (lastWriteTime1
< lastWriteTime2
)
200 if(!sameType(sFileNameDest
, algo
, wantMipMap
))
202 return false; // file exists but a new compression type is required
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";
218 return inputFileName
.substr(0,pos
) + ".dds";
222 // ***************************************************************************
223 void dividSize (CBitmap
&bitmap
)
226 nlassert (bitmap
.getPixelFormat () == CBitmap::RGBA
);
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
);
239 uint8
*pixelSrc
= &(temp
.getPixels ()[0]);
240 uint8
*pixelDest
= &(bitmap
.getPixels ()[0]);
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;
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] = {
265 const int bayerDiv8G
[4][4] = {
272 const int bayerDiv8B
[4][4] = {
279 // ***************************************************************************
280 int main(int argc
, char **argv
)
282 CApplicationContext applicationContext
;
284 // Parse Command Line.
285 //====================
286 NLMISC::CCmdArgs args
;
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"
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"
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;
319 if (args
.haveArg("o"))
320 OptOutputFileName
= args
.getArg("o").front();
322 if (args
.haveArg("m"))
325 if (args
.haveArg("g"))
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
;
342 cerr
<< "Unknown algorithm: " << strAlgo
<< endl
;
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
)
364 // Reading TGA or PNG and converting to RGBA
365 //====================================
370 std::string inputFileName
= inputFileNames
[i
];
372 if(inputFileName
.find("_usercolor")<inputFileName
.length())
377 NLMISC::CIFile input
;
378 if(!input
.open(inputFileName
))
380 cerr
<<"Can't open input file " << inputFileName
<< endl
;
384 // allow to load an image as grayscale instead of alpha
385 if (OptGrayscale
) picTga
.loadGrayscaleAsAlpha(false);
387 uint8 imageDepth
= picTga
.load(input
);
390 cerr
<<"Can't load file: "<<inputFileName
<<endl
;
393 if(imageDepth
!=16 && imageDepth
!=24 && imageDepth
!=32 && imageDepth
!=8)
395 cerr
<<"Image not supported: "<<imageDepth
<<endl
;
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
));
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
;
428 outputFileName
= getOutputFileName(inputFileName
);
432 if (OptAlgo
==NOT_DEFINED
)
433 OptAlgo
= getType (outputFileName
);
436 if(OptAlgo
!=NOT_DEFINED
)
442 // TODO: if alpha channel is 0, use DXTC1a instead DXTC1
451 if(dataCheck(inputFileName
,outputFileName
, OptAlgo
, OptMipMap
))
453 cout
<<outputFileName
<<" : a recent dds file already exists"<<endl
;
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;
468 // Checking if option "usercolor" has been used
469 std::string userColorFileName;
472 if(strcmp("-usercolor",argv[4])==0)
479 userColorFileName = argv[5];
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";
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
;
532 /*uint8 F = (uint8) ((float)pRGBASrc[i].R*0.3 + (float)pRGBASrc[i].G*0.56 + (float)pRGBASrc[i].B*0.14);
534 if((F*pRGBASrc2[i].A/255)==255)
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
;
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;
565 // Else special case: At==0, and Lt==1.
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
);
581 RGBADest
[dstRGBADestId
++]= r
;
582 RGBADest
[dstRGBADestId
++]= g
;
583 RGBADest
[dstRGBADestId
++]= b
;
584 RGBADest
[dstRGBADestId
++]= a
;
591 // Copy to the dest bitmap.
592 picSrc
.resize(width
, height
, CBitmap::RGBA
);
593 picSrc
.getPixels(0)= RGBADest
;
595 // Resize the destination bitmap ?
604 // Apply bayer dither
605 CObjectVector
<uint8
> &rgba
= picSrc
.getPixels(0);
606 const uint32 w
= picSrc
.getWidth(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]);
622 // 8 or 16 bits TGA or PNG ?
623 if ((algo
== TGA16
) || (algo
== TGA8
) || (algo
== PNG16
) || (algo
== PNG8
))
625 // Saving TGA or PNG file
627 NLMISC::COFile output
;
628 if(!output
.open(outputFileName
))
630 cerr
<<"Can't open output file "<<outputFileName
<<endl
;
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
;
684 cout
<<"compressing ("<<algostr
<<") "<<inputFileName
<<" to "<<outputFileName
<<endl
;
687 // Saving compressed DDS file
689 NLMISC::COFile output
;
690 if(!output
.open(outputFileName
))
692 cerr
<<"Can't open output file "<<outputFileName
<<endl
;
697 CS3TCCompressor comp
;
698 comp
.compress(picSrc
, OptMipMap
, algo
, output
);
700 catch(const NLMISC::EWriteError
&e
)
702 cerr
<<e
.what()<<endl
;