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