1 /* -----------------------------------------------------------------------------
2
3 Copyright (c) 2006 Simon Brown si@sjbrown.co.uk
4
5 Permission is hereby granted, free of charge, to any person obtaining
6 a copy of this software and associated documentation files (the
7 "Software"), to deal in the Software without restriction, including
8 without limitation the rights to use, copy, modify, merge, publish,
9 distribute, sublicense, and/or sell copies of the Software, and to
10 permit persons to whom the Software is furnished to do so, subject to
11 the following conditions:
12
13 The above copyright notice and this permission notice shall be included
14 in all copies or substantial portions of the Software.
15
16 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20 CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21 TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22 SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
24 -------------------------------------------------------------------------- */
25
26 /*! @file
27
28 @brief Example program that converts between the PNG and DXT formats.
29
30 This program requires libpng for PNG input and output, and is designed
31 to show how to prepare data for the squish library when it is not simply
32 a contiguous block of memory.
33 */
34
35 #include <iostream>
36 #include <string>
37 #include <sstream>
38 #include <ctime>
39 #include <cmath>
40 #include <squish.h>
41 #include <png.h>
42
43 #ifdef _MSC_VER
44 #pragma warning( disable: 4511 4512 )
45 #endif // def _MSC_VER
46
47 using namespace squish;
48
49 //! Simple exception class.
50 class Error : public std::exception
51 {
52 public:
Error(std::string const & excuse)53 Error( std::string const& excuse ) : m_excuse( excuse ) {}
~Error()54 ~Error() throw() {}
55
what() const56 virtual char const* what() const throw() { return m_excuse.c_str(); }
57
58 private:
59 std::string m_excuse;
60 };
61
62 //! Base class to make derived classes non-copyable
63 class NonCopyable
64 {
65 public:
NonCopyable()66 NonCopyable() {}
67
68 private:
69 NonCopyable( NonCopyable const& );
70 NonCopyable& operator=( NonCopyable const& );
71 };
72
73 //! Memory object.
74 class Mem : NonCopyable
75 {
76 public:
Mem(int size)77 explicit Mem( int size ) : m_p( new u8[size] ) {}
~Mem()78 ~Mem() { delete[] m_p; }
79
Get() const80 u8* Get() const { return m_p; }
81
82 private:
83 u8* m_p;
84 };
85
86 //! File object.
87 class File : NonCopyable
88 {
89 public:
File(FILE * fp)90 explicit File( FILE* fp ) : m_fp( fp ) {}
~File()91 ~File() { if( m_fp ) fclose( m_fp ); }
92
IsValid() const93 bool IsValid() const { return m_fp != 0; }
Get() const94 FILE* Get() const { return m_fp; }
95
96 private:
97 FILE* m_fp;
98 };
99
100 //! PNG read object.
101 class PngReadStruct : NonCopyable
102 {
103 public:
PngReadStruct()104 PngReadStruct()
105 : m_png( 0 ),
106 m_info( 0 ),
107 m_end( 0 )
108 {
109 m_png = png_create_read_struct( PNG_LIBPNG_VER_STRING, 0, 0, 0 );
110 if( !m_png )
111 throw Error( "failed to create png read struct" );
112
113 m_info = png_create_info_struct( m_png );
114 m_end = png_create_info_struct( m_png );
115 if( !m_info || !m_end )
116 {
117 png_infopp info = m_info ? &m_info : 0;
118 png_infopp end = m_end ? &m_end : 0;
119 png_destroy_read_struct( &m_png, info, end );
120 throw Error( "failed to create png info structs" );
121 }
122 }
123
~PngReadStruct()124 ~PngReadStruct()
125 {
126 png_destroy_read_struct( &m_png, &m_info, &m_end );
127 }
128
GetPng() const129 png_structp GetPng() const { return m_png; }
GetInfo() const130 png_infop GetInfo() const { return m_info; }
131
132 private:
133 png_structp m_png;
134 png_infop m_info, m_end;
135 };
136
137 //! PNG write object.
138 class PngWriteStruct : NonCopyable
139 {
140 public:
PngWriteStruct()141 PngWriteStruct()
142 : m_png( 0 ),
143 m_info( 0 )
144 {
145 m_png = png_create_write_struct( PNG_LIBPNG_VER_STRING, 0, 0, 0 );
146 if( !m_png )
147 throw Error( "failed to create png read struct" );
148
149 m_info = png_create_info_struct( m_png );
150 if( !m_info )
151 {
152 png_infopp info = m_info ? &m_info : 0;
153 png_destroy_write_struct( &m_png, info );
154 throw Error( "failed to create png info structs" );
155 }
156 }
157
~PngWriteStruct()158 ~PngWriteStruct()
159 {
160 png_destroy_write_struct( &m_png, &m_info );
161 }
162
GetPng() const163 png_structp GetPng() const { return m_png; }
GetInfo() const164 png_infop GetInfo() const { return m_info; }
165
166 private:
167 png_structp m_png;
168 png_infop m_info;
169 };
170
171 //! PNG rows object.
172 class PngRows : NonCopyable
173 {
174 public:
PngRows(int width,int height,int stride)175 PngRows( int width, int height, int stride ) : m_width( width ), m_height( height )
176 {
177 m_rows = ( png_bytep* )malloc( m_height*sizeof( png_bytep ) );
178 for( int i = 0; i < m_height; ++i )
179 m_rows[i] = ( png_bytep )malloc( m_width*stride );
180 }
181
~PngRows()182 ~PngRows()
183 {
184 for( int i = 0; i < m_height; ++i )
185 free( m_rows[i] );
186 free( m_rows );
187 }
188
Get() const189 png_bytep* Get() const { return m_rows; }
190
191 private:
192 png_bytep* m_rows;
193 int m_width, m_height;
194 };
195
196 class PngImage
197 {
198 public:
199 explicit PngImage( std::string const& fileName );
200
GetWidth() const201 int GetWidth() const { return m_width; }
GetHeight() const202 int GetHeight() const { return m_height; }
GetStride() const203 int GetStride() const { return m_stride; }
IsColour() const204 bool IsColour() const { return m_colour; }
IsAlpha() const205 bool IsAlpha() const { return m_alpha; }
206
GetRow(int row) const207 u8 const* GetRow( int row ) const { return ( u8* )m_rows[row]; }
208
209 private:
210 PngReadStruct m_png;
211
212 int m_width;
213 int m_height;
214 int m_stride;
215 bool m_colour;
216 bool m_alpha;
217
218 png_bytep* m_rows;
219 };
220
PngImage(std::string const & fileName)221 PngImage::PngImage( std::string const& fileName )
222 {
223 // open the source file
224 File file( fopen( fileName.c_str(), "rb" ) );
225 if( !file.IsValid() )
226 {
227 std::ostringstream oss;
228 oss << "failed to open \"" << fileName << "\" for reading";
229 throw Error( oss.str() );
230 }
231
232 // check the signature bytes
233 png_byte header[8];
234 fread( header, 1, 8, file.Get() );
235 if( png_sig_cmp( header, 0, 8 ) )
236 {
237 std::ostringstream oss;
238 oss << "\"" << fileName << "\" does not look like a png file";
239 throw Error( oss.str() );
240 }
241
242 // read the image into memory
243 png_init_io( m_png.GetPng(), file.Get() );
244 png_set_sig_bytes( m_png.GetPng(), 8 );
245 png_read_png( m_png.GetPng(), m_png.GetInfo(), PNG_TRANSFORM_EXPAND, 0 );
246
247 // get the image info
248 png_uint_32 width;
249 png_uint_32 height;
250 int bitDepth;
251 int colourType;
252 png_get_IHDR( m_png.GetPng(), m_png.GetInfo(), &width, &height, &bitDepth, &colourType, 0, 0, 0 );
253
254 // check the image is 8 bit
255 if( bitDepth != 8 )
256 {
257 std::ostringstream oss;
258 oss << "cannot process " << bitDepth << "-bit image (bit depth must be 8)";
259 throw Error( oss.str() );
260 }
261
262 // save the info
263 m_width = width;
264 m_height = height;
265 m_colour = ( ( colourType & PNG_COLOR_MASK_COLOR ) != 0 );
266 m_alpha = ( ( colourType & PNG_COLOR_MASK_ALPHA ) != 0 );
267 m_stride = ( m_colour ? 3 : 1 ) + ( m_alpha ? 1 : 0 );
268
269 // get the image rows
270 m_rows = png_get_rows( m_png.GetPng(), m_png.GetInfo() );
271 if( !m_rows )
272 throw Error( "failed to get image rows" );
273 }
274
Compress(std::string const & sourceFileName,std::string const & targetFileName,int flags)275 static void Compress( std::string const& sourceFileName, std::string const& targetFileName, int flags )
276 {
277 // load the source image
278 PngImage sourceImage( sourceFileName );
279
280 // get the image info
281 int width = sourceImage.GetWidth();
282 int height = sourceImage.GetHeight();
283 int stride = sourceImage.GetStride();
284 bool colour = sourceImage.IsColour();
285 bool alpha = sourceImage.IsAlpha();
286
287 // check the image dimensions
288 if( ( width % 4 ) != 0 || ( height % 4 ) != 0 )
289 {
290 std::ostringstream oss;
291 oss << "cannot compress " << width << "x" << height
292 << "image (dimensions must be multiples of 4)";
293 throw Error( oss.str() );
294 }
295
296 // create the target data
297 int bytesPerBlock = ( ( flags & kDxt1 ) != 0 ) ? 8 : 16;
298 int targetDataSize = bytesPerBlock*width*height/16;
299 Mem targetData( targetDataSize );
300
301 // loop over blocks and compress them
302 clock_t start = std::clock();
303 u8* targetBlock = targetData.Get();
304 for( int y = 0; y < height; y += 4 )
305 {
306 // process a row of blocks
307 for( int x = 0; x < width; x += 4 )
308 {
309 // get the block data
310 u8 sourceRgba[16*4];
311 for( int py = 0, i = 0; py < 4; ++py )
312 {
313 u8 const* row = sourceImage.GetRow( y + py ) + x*stride;
314 for( int px = 0; px < 4; ++px, ++i )
315 {
316 // get the pixel colour
317 if( colour )
318 {
319 for( int j = 0; j < 3; ++j )
320 sourceRgba[4*i + j] = *row++;
321 }
322 else
323 {
324 for( int j = 0; j < 3; ++j )
325 sourceRgba[4*i + j] = *row;
326 ++row;
327 }
328
329 // skip alpha for now
330 if( alpha )
331 sourceRgba[4*i + 3] = *row++;
332 else
333 sourceRgba[4*i + 3] = 255;
334 }
335 }
336
337 // compress this block
338 Compress( sourceRgba, targetBlock, flags );
339
340 // advance
341 targetBlock += bytesPerBlock;
342 }
343 }
344 clock_t end = std::clock();
345 double duration = ( double )( end - start ) / CLOCKS_PER_SEC;
346 std::cout << "time taken: " << duration << " seconds" << std::endl;
347
348 // open the target file
349 File targetFile( fopen( targetFileName.c_str(), "wb" ) );
350 if( !targetFile.IsValid() )
351 {
352 std::ostringstream oss;
353 oss << "failed to open \"" << sourceFileName << "\" for writing";
354 throw Error( oss.str() );
355 }
356
357 // write the header
358 fwrite( &width, sizeof( int ), 1, targetFile.Get() );
359 fwrite( &height, sizeof( int ), 1, targetFile.Get() );
360
361 // write the data
362 fwrite( targetData.Get(), 1, targetDataSize, targetFile.Get() );
363 }
364
Decompress(std::string const & sourceFileName,std::string const & targetFileName,int flags)365 static void Decompress( std::string const& sourceFileName, std::string const& targetFileName, int flags )
366 {
367 // open the source file
368 File sourceFile( fopen( sourceFileName.c_str(), "rb" ) );
369 if( !sourceFile.IsValid() )
370 {
371 std::ostringstream oss;
372 oss << "failed to open \"" << sourceFileName << "\" for reading";
373 throw Error( oss.str() );
374 }
375
376 // get the width and height
377 int width, height;
378 fread( &width, sizeof( int ), 1, sourceFile.Get() );
379 fread( &height, sizeof( int ), 1, sourceFile.Get() );
380
381 // work out the data size
382 int bytesPerBlock = ( ( flags & kDxt1 ) != 0 ) ? 8 : 16;
383 int sourceDataSize = bytesPerBlock*width*height/16;
384 Mem sourceData( sourceDataSize );
385
386 // read the source data
387 fread( sourceData.Get(), 1, sourceDataSize, sourceFile.Get() );
388
389 // create the target rows
390 PngRows targetRows( width, height, 4 );
391
392 // loop over blocks and compress them
393 u8 const* sourceBlock = sourceData.Get();
394 for( int y = 0; y < height; y += 4 )
395 {
396 // process a row of blocks
397 for( int x = 0; x < width; x += 4 )
398 {
399 // decompress back
400 u8 targetRgba[16*4];
401 Decompress( targetRgba, sourceBlock, flags );
402
403 // write the data into the target rows
404 for( int py = 0, i = 0; py < 4; ++py )
405 {
406 u8* row = ( u8* )targetRows.Get()[y + py] + x*4;
407 for( int px = 0; px < 4; ++px, ++i )
408 {
409 for( int j = 0; j < 4; ++j )
410 *row++ = targetRgba[4*i + j];
411 }
412 }
413
414 // advance
415 sourceBlock += bytesPerBlock;
416 }
417 }
418
419 // create the target PNG
420 PngWriteStruct targetPng;
421
422 // set up the image
423 png_set_IHDR(
424 targetPng.GetPng(), targetPng.GetInfo(), width, height,
425 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE,
426 PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT
427 );
428
429 // open the target file
430 File targetFile( fopen( targetFileName.c_str(), "wb" ) );
431 if( !targetFile.IsValid() )
432 {
433 std::ostringstream oss;
434 oss << "failed to open \"" << targetFileName << "\" for writing";
435 throw Error( oss.str() );
436 }
437
438 // write the image
439 png_set_rows( targetPng.GetPng(), targetPng.GetInfo(), targetRows.Get() );
440 png_init_io( targetPng.GetPng(), targetFile.Get() );
441 png_write_png( targetPng.GetPng(), targetPng.GetInfo(), PNG_TRANSFORM_IDENTITY, 0 );
442 }
443
Diff(std::string const & sourceFileName,std::string const & targetFileName)444 static void Diff( std::string const& sourceFileName, std::string const& targetFileName )
445 {
446 // load the images
447 PngImage sourceImage( sourceFileName );
448 PngImage targetImage( targetFileName );
449
450 // get the image info
451 int width = sourceImage.GetWidth();
452 int height = sourceImage.GetHeight();
453 int sourceStride = sourceImage.GetStride();
454 int targetStride = targetImage.GetStride();
455 int stride = std::min( sourceStride, targetStride );
456
457 // check they match
458 if( width != targetImage.GetWidth() || height != targetImage.GetHeight() )
459 throw Error( "source and target dimensions do not match" );
460
461 // work out the error
462 double error = 0.0;
463 for( int y = 0; y < height; ++y )
464 {
465 u8 const* sourceRow = sourceImage.GetRow( y );
466 u8 const* targetRow = targetImage.GetRow( y );
467 for( int x = 0; x < width; ++x )
468 {
469 u8 const* sourcePixel = sourceRow + x*sourceStride;
470 u8 const* targetPixel = targetRow + x*targetStride;
471 for( int i = 0; i < stride; ++i )
472 {
473 int diff = ( int )sourcePixel[i] - ( int )targetPixel[i];
474 error += ( double )( diff*diff );
475 }
476 }
477 }
478 error = std::sqrt( error / ( width*height ) );
479
480 // print it out
481 std::cout << "rms error: " << error << std::endl;
482 }
483
484 enum Mode
485 {
486 kCompress,
487 kDecompress,
488 kDiff
489 };
490
main(int argc,char * argv[])491 int main( int argc, char* argv[] )
492 {
493 try
494 {
495 // parse the command-line
496 std::string sourceFileName;
497 std::string targetFileName;
498 Mode mode = kCompress;
499 int method = kDxt1;
500 int metric = kColourMetricPerceptual;
501 int fit = kColourClusterFit;
502 int extra = 0;
503 bool help = false;
504 bool arguments = true;
505 for( int i = 1; i < argc; ++i )
506 {
507 // check for options
508 char const* word = argv[i];
509 if( arguments && word[0] == '-' )
510 {
511 for( int j = 1; word[j] != '\0'; ++j )
512 {
513 switch( word[j] )
514 {
515 case 'h': help = true; break;
516 case 'c': mode = kCompress; break;
517 case 'd': mode = kDecompress; break;
518 case 'e': mode = kDiff; break;
519 case '1': method = kDxt1; break;
520 case '3': method = kDxt3; break;
521 case '5': method = kDxt5; break;
522 case 'u': metric = kColourMetricUniform; break;
523 case 'r': fit = kColourRangeFit; break;
524 case 'w': extra = kWeightColourByAlpha; break;
525 case '-': arguments = false; break;
526 default:
527 std::cerr << "unknown option '" << word[j] << "'" << std::endl;
528 return -1;
529 }
530 }
531 }
532 else
533 {
534 if( sourceFileName.empty() )
535 sourceFileName.assign( word );
536 else if( targetFileName.empty() )
537 targetFileName.assign( word );
538 else
539 {
540 std::cerr << "unexpected argument \"" << word << "\"" << std::endl;
541 }
542 }
543 }
544
545 // check arguments
546 if( help )
547 {
548 std::cout
549 << "SYNTAX" << std::endl
550 << "\tsquishpng [-cde135] <source> <target>" << std::endl
551 << "OPTIONS" << std::endl
552 << "\t-c\tCompress source png to target raw dxt (default)" << std::endl
553 << "\t-135\tSpecifies whether to use DXT1 (default), DXT3 or DXT5 compression" << std::endl
554 << "\t-u\tUse a uniform colour metric during colour compression" << std::endl
555 << "\t-r\tUse the fast but inferior range-based colour compressor" << std::endl
556 << "\t-w\tWeight colour values by alpha in the cluster colour compressor" << std::endl
557 << "\t-d\tDecompress source raw dxt to target png" << std::endl
558 << "\t-e\tDiff source and target png" << std::endl
559 ;
560
561 return 0;
562 }
563 if( sourceFileName.empty() )
564 {
565 std::cerr << "no source file given" << std::endl;
566 return -1;
567 }
568 if( targetFileName.empty() )
569 {
570 std::cerr << "no target file given" << std::endl;
571 return -1;
572 }
573
574 // do the work
575 switch( mode )
576 {
577 case kCompress:
578 Compress( sourceFileName, targetFileName, method | metric | fit | extra );
579 break;
580
581 case kDecompress:
582 Decompress( sourceFileName, targetFileName, method );
583 break;
584
585 case kDiff:
586 Diff( sourceFileName, targetFileName );
587 break;
588
589 default:
590 std::cerr << "unknown mode" << std::endl;
591 throw std::exception();
592 }
593 }
594 catch( std::exception& excuse )
595 {
596 // complain
597 std::cerr << "squishpng error: " << excuse.what() << std::endl;
598 return -1;
599 }
600
601 // done
602 return 0;
603 }
604