PageRenderTime 62ms CodeModel.GetById 2ms app.highlight 53ms RepoModel.GetById 1ms app.codeStats 1ms

/indra/integration_tests/llimage_libtest/llimage_libtest.cpp

https://bitbucket.org/lindenlab/viewer-beta/
C++ | 561 lines | 440 code | 47 blank | 74 comment | 134 complexity | bfe2a4e54ea646f9affbac5f9017e9a3 MD5 | raw file
  1/** 
  2 * @file llimage_libtest.cpp
  3 * @author Merov Linden
  4 * @brief Integration test for the llimage library
  5 *
  6 * $LicenseInfo:firstyear=2011&license=viewerlgpl$
  7 * Second Life Viewer Source Code
  8 * Copyright (C) 2011, Linden Research, Inc.
  9 * 
 10 * This library is free software; you can redistribute it and/or
 11 * modify it under the terms of the GNU Lesser General Public
 12 * License as published by the Free Software Foundation;
 13 * version 2.1 of the License only.
 14 * 
 15 * This library is distributed in the hope that it will be useful,
 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 18 * Lesser General Public License for more details.
 19 * 
 20 * You should have received a copy of the GNU Lesser General Public
 21 * License along with this library; if not, write to the Free Software
 22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 23 * 
 24 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 25 * $/LicenseInfo$
 26 */
 27#include "linden_common.h"
 28#include "llpointer.h"
 29#include "lltimer.h"
 30
 31#include "llimage_libtest.h"
 32
 33// Linden library includes
 34#include "llimage.h"
 35#include "llimagejpeg.h"
 36#include "llimagepng.h"
 37#include "llimagebmp.h"
 38#include "llimagetga.h"
 39#include "llimagej2c.h"
 40#include "lldir.h"
 41#include "lldiriterator.h"
 42
 43// system libraries
 44#include <iostream>
 45
 46// doc string provided when invoking the program with --help 
 47static const char USAGE[] = "\n"
 48"usage:\tllimage_libtest [options]\n"
 49"\n"
 50" -h, --help\n"
 51"        Print this help\n"
 52" -i, --input <file1 .. file2>\n"
 53"        List of image files to load and convert. Patterns with wild cards can be used.\n"
 54" -o, --output <file1 .. file2> OR <type>\n"
 55"        List of image files to create (assumes same order as for input files)\n"
 56"        OR 3 letters file type extension to convert each input file into.\n"
 57" -r, --region <x0, y0, x1, y1>\n"
 58"        Crop region applied to the input files in pixels.\n"
 59"        Only used for j2c images. Default is no region cropping.\n"
 60" -d, --discard_level <n>\n"
 61"        Discard level max used on input. 0 is highest resolution. Max discard level is 5.\n"
 62"        This allows the input image to be clamped in resolution when loading.\n"
 63"        Only valid for j2c images. Default is no discard.\n"
 64" -p, --precincts <n>\n"
 65"        Dimension of precincts in pixels. Precincts are assumed square and identical for\n"
 66"        all levels. Note that this option also add PLT and tile markers to the codestream, \n"
 67"        and uses RPCL order. Power of 2 must be used.\n"
 68"        Only valid for output j2c images. Default is no precincts used.\n"
 69" -b, --blocks <n>\n"
 70"        Dimension of coding blocks in pixels. Blocks are assumed square. Power of 2 must\n"
 71"        be used. Blocks must be smaller than precincts. Like precincts, this option adds\n"
 72"        PLT, tile markers and uses RPCL.\n"
 73"        Only valid for output j2c images. Default is 64.\n"
 74" -l, --levels <n>\n"
 75"        Number of decomposition levels (aka discard levels) in the output image.\n"
 76"        The maximum number of levels authorized is 32.\n"
 77"        Only valid for output j2c images. Default is 5.\n"
 78" -rev, --reversible\n"
 79"        Set the compression to be lossless (reversible in j2c parlance).\n"
 80"        Only valid for output j2c images.\n"
 81" -log, --logmetrics <metric>\n"
 82"        Log performance data for <metric>. Results in <metric>.slp\n"
 83"        Note: so far, only ImageCompressionTester has been tested.\n"
 84" -a, --analyzeperformance\n"
 85"        Create a report comparing <metric>_baseline.slp with current <metric>.slp\n"
 86"        Results in <metric>_report.csv\n"
 87" -s, --image-stats\n"
 88"        Output stats for each input and output image.\n"
 89"\n";
 90
 91// true when all image loading is done. Used by metric logging thread to know when to stop the thread.
 92static bool sAllDone = false;
 93
 94// Create an empty formatted image instance of the correct type from the filename
 95LLPointer<LLImageFormatted> create_image(const std::string &filename)
 96{
 97	std::string exten = gDirUtilp->getExtension(filename);	
 98	LLPointer<LLImageFormatted> image = LLImageFormatted::createFromExtension(exten);
 99	return image;
100}
101
102void output_image_stats(LLPointer<LLImageFormatted> image, const std::string &filename)
103{
104	// Print out some statistical data on the image
105	std::cout << "Image stats for : " << filename << ", extension : " << image->getExtension() << std::endl;
106
107	std::cout << "    with : " << (int)(image->getWidth())       << ", height : " << (int)(image->getHeight())       << std::endl;
108	std::cout << "    comp : " << (int)(image->getComponents())  << ", levels : " << (int)(image->getDiscardLevel()) << std::endl;
109	std::cout << "    head : " << (int)(image->calcHeaderSize()) << ",   data : " << (int)(image->getDataSize())     << std::endl;
110
111	return;
112}
113
114// Load an image from file and return a raw (decompressed) instance of its data
115LLPointer<LLImageRaw> load_image(const std::string &src_filename, int discard_level, int* region, bool output_stats)
116{
117	LLPointer<LLImageFormatted> image = create_image(src_filename);
118	
119	// This just loads the image file stream into a buffer. No decoding done.
120	if (!image->load(src_filename))
121	{
122		return NULL;
123	}
124	
125	if(	(image->getComponents() != 3) && (image->getComponents() != 4) )
126	{
127		std::cout << "Image files with less than 3 or more than 4 components are not supported\n";
128		return NULL;
129	}
130	
131	if (output_stats)
132	{
133		output_image_stats(image, src_filename);
134	}
135	
136	LLPointer<LLImageRaw> raw_image = new LLImageRaw;
137	
138	// Set the image restriction on load in the case of a j2c image
139	if ((image->getCodec() == IMG_CODEC_J2C) && ((discard_level != -1) || (region != NULL)))
140	{
141		// That method doesn't exist (and likely, doesn't make sense) for any other image file format
142		// hence the required cryptic cast.
143		((LLImageJ2C*)(image.get()))->initDecode(*raw_image, discard_level, region);
144	}
145	
146	if (!image->decode(raw_image, 0.0f))
147	{
148		return NULL;
149	}
150	
151	return raw_image;
152}
153
154// Save a raw image instance into a file
155bool save_image(const std::string &dest_filename, LLPointer<LLImageRaw> raw_image, int blocks_size, int precincts_size, int levels, bool reversible, bool output_stats)
156{
157	LLPointer<LLImageFormatted> image = create_image(dest_filename);
158	
159	// Set the image codestream parameters on output in the case of a j2c image
160	if (image->getCodec() == IMG_CODEC_J2C)
161	{
162		// That method doesn't exist (and likely, doesn't make sense) for any other image file format
163		// hence the required cryptic cast.
164		if ((blocks_size != -1) || (precincts_size != -1) || (levels != 0))
165		{
166			((LLImageJ2C*)(image.get()))->initEncode(*raw_image, blocks_size, precincts_size, levels);
167		}
168		((LLImageJ2C*)(image.get()))->setReversible(reversible);
169	}
170	
171	if (!image->encode(raw_image, 0.0f))
172	{
173		return false;
174	}
175	
176	if (output_stats)
177	{
178		output_image_stats(image, dest_filename);
179	}
180
181	return image->save(dest_filename);
182}
183
184void store_input_file(std::list<std::string> &input_filenames, const std::string &path)
185{
186	// Break the incoming path in its components
187	std::string dir = gDirUtilp->getDirName(path);
188	std::string name = gDirUtilp->getBaseFileName(path);
189	std::string exten = gDirUtilp->getExtension(path);
190
191	// std::cout << "store_input_file : " << path << ", dir : " << dir << ", name : " << name << ", exten : " << exten << std::endl;
192	
193	// If extension is not an image type or "*", exit
194	// Note: we don't support complex patterns for the extension like "j??"
195	// Note: on most shells, the pattern expansion is done by the shell so that pattern matching limitation is actually not a problem
196	if ((exten.compare("*") != 0) && (LLImageBase::getCodecFromExtension(exten) == IMG_CODEC_INVALID))
197	{
198		return;
199	}
200
201	if ((name.find('*') != -1) || ((name.find('?') != -1)))
202	{
203		// If file name is a pattern, iterate to get each file name and store
204		std::string next_name;
205		LLDirIterator iter(dir, name);
206		while (iter.next(next_name))
207		{
208			std::string file_name = dir + gDirUtilp->getDirDelimiter() + next_name;
209			input_filenames.push_back(file_name);
210		}
211	}
212	else
213	{
214		// Verify that the file does exist before storing 
215		if (gDirUtilp->fileExists(path))
216		{
217			input_filenames.push_back(path);
218		}
219		else
220		{
221			std::cout << "store_input_file : the file " << path << " could not be found" << std::endl;
222		}
223	}	
224}
225
226void store_output_file(std::list<std::string> &output_filenames, std::list<std::string> &input_filenames, const std::string &path)
227{
228	// Break the incoming path in its components
229	std::string dir = gDirUtilp->getDirName(path);
230	std::string name = gDirUtilp->getBaseFileName(path);
231	std::string exten = gDirUtilp->getExtension(path);
232	
233	// std::cout << "store_output_file : " << path << ", dir : " << dir << ", name : " << name << ", exten : " << exten << std::endl;
234	
235	if (dir.empty() && exten.empty())
236	{
237		// If dir and exten are empty, we interpret the name as a file extension type name and will iterate through input list to populate the output list
238		exten = name;
239		// Make sure the extension is an image type
240		if (LLImageBase::getCodecFromExtension(exten) == IMG_CODEC_INVALID)
241		{
242			return;
243		}
244		std::string delim = gDirUtilp->getDirDelimiter();
245		std::list<std::string>::iterator in_file  = input_filenames.begin();
246		std::list<std::string>::iterator end = input_filenames.end();
247		for (; in_file != end; ++in_file)
248		{
249			dir = gDirUtilp->getDirName(*in_file);
250			name = gDirUtilp->getBaseFileName(*in_file,true);
251			std::string file_name;
252			if (!dir.empty())
253			{
254				file_name = dir + delim + name + "." + exten;
255			}
256			else
257			{
258				file_name = name + "." + exten;
259			}
260			output_filenames.push_back(file_name);
261		}
262	}
263	else
264	{
265		// Make sure the extension is an image type
266		if (LLImageBase::getCodecFromExtension(exten) == IMG_CODEC_INVALID)
267		{
268			return;
269		}
270		// Store the path
271		output_filenames.push_back(path);
272	}
273}
274
275// Holds the metric gathering output in a thread safe way
276class LogThread : public LLThread
277{
278public:
279	std::string mFile;
280
281	LogThread(std::string& test_name) : LLThread("llimage_libtest log")
282	{
283		std::string file_name = test_name + std::string(".slp");
284		mFile = file_name;
285	}
286		
287	void run()
288	{
289		std::ofstream os(mFile.c_str());
290			
291		while (!sAllDone)
292		{
293			LLFastTimer::writeLog(os);
294			os.flush();
295			ms_sleep(32);
296		}
297		LLFastTimer::writeLog(os);
298		os.flush();
299		os.close();
300	}		
301};
302
303int main(int argc, char** argv)
304{
305	// List of input and output files
306	std::list<std::string> input_filenames;
307	std::list<std::string> output_filenames;
308	// Other optional parsed arguments
309	bool analyze_performance = false;
310	bool image_stats = false;
311	int* region = NULL;
312	int discard_level = -1;
313	int precincts_size = -1;
314	int blocks_size = -1;
315	int levels = 0;
316	bool reversible = false;
317
318	// Init whatever is necessary
319	ll_init_apr();
320	LLImage::initClass();
321	LogThread* fast_timer_log_thread = NULL;	// For performance and metric gathering
322
323	// Analyze command line arguments
324	for (int arg = 1; arg < argc; ++arg)
325	{
326		if (!strcmp(argv[arg], "--help") || !strcmp(argv[arg], "-h"))
327		{
328            // Send the usage to standard out
329            std::cout << USAGE << std::endl;
330			return 0;
331		}
332		else if ((!strcmp(argv[arg], "--input") || !strcmp(argv[arg], "-i")) && arg < argc-1)
333		{
334			std::string file_name = argv[arg+1];
335			while (file_name[0] != '-')		// if arg starts with '-', we consider it's not a file name but some other argument
336			{
337				// std::cout << "input file name : " << file_name << std::endl;				
338				store_input_file(input_filenames, file_name);
339				arg += 1;					// Skip that arg now we know it's a file name
340				if ((arg + 1) == argc)		// Break out of the loop if we reach the end of the arg list
341					break;
342				file_name = argv[arg+1];	// Next argument and loop over
343			}
344		}
345		else if ((!strcmp(argv[arg], "--output") || !strcmp(argv[arg], "-o")) && arg < argc-1)
346		{
347			std::string file_name = argv[arg+1];
348			while (file_name[0] != '-')		// if arg starts with '-', we consider it's not a file name but some other argument
349			{
350				// std::cout << "output file name : " << file_name << std::endl;				
351				store_output_file(output_filenames, input_filenames, file_name);
352				arg += 1;					// Skip that arg now we know it's a file name
353				if ((arg + 1) == argc)		// Break out of the loop if we reach the end of the arg list
354					break;
355				file_name = argv[arg+1];	// Next argument and loop over
356			}
357		}
358		else if ((!strcmp(argv[arg], "--region") || !strcmp(argv[arg], "-r")) && arg < argc-1)
359		{
360			std::string value_str = argv[arg+1];
361			int index = 0;
362			region = new int[4];
363			while (value_str[0] != '-')		// if arg starts with '-', it's the next option
364			{
365				int value = atoi(value_str.c_str());
366				region[index++] = value;
367				arg += 1;					// Definitely skip that arg now we know it's a number
368				if ((arg + 1) == argc)		// Break out of the loop if we reach the end of the arg list
369					break;
370				if (index == 4)				// Break out of the loop if we captured 4 values already
371					break;
372				value_str = argv[arg+1];	// Next argument and loop over
373			}
374			if (index != 4)
375			{
376				std::cout << "--region arguments invalid" << std::endl;
377				delete [] region;
378				region = NULL;
379			}
380		}
381		else if (!strcmp(argv[arg], "--discard_level") || !strcmp(argv[arg], "-d"))
382		{
383			std::string value_str;
384			if ((arg + 1) < argc)
385			{
386				value_str = argv[arg+1];
387			}
388			if (((arg + 1) >= argc) || (value_str[0] == '-'))
389			{
390				std::cout << "No valid --discard_level argument given, discard_level ignored" << std::endl;
391			}
392			else
393			{
394				discard_level = atoi(value_str.c_str());
395				// Clamp to the values accepted by the viewer
396				discard_level = llclamp(discard_level,0,5);
397			}
398		}
399		else if (!strcmp(argv[arg], "--precincts") || !strcmp(argv[arg], "-p"))
400		{
401			std::string value_str;
402			if ((arg + 1) < argc)
403			{
404				value_str = argv[arg+1];
405			}
406			if (((arg + 1) >= argc) || (value_str[0] == '-'))
407			{
408				std::cout << "No valid --precincts argument given, precincts ignored" << std::endl;
409			}
410			else
411			{
412				precincts_size = atoi(value_str.c_str());
413			}
414		}
415		else if (!strcmp(argv[arg], "--blocks") || !strcmp(argv[arg], "-b"))
416		{
417			std::string value_str;
418			if ((arg + 1) < argc)
419			{
420				value_str = argv[arg+1];
421			}
422			if (((arg + 1) >= argc) || (value_str[0] == '-'))
423			{
424				std::cout << "No valid --blocks argument given, blocks ignored" << std::endl;
425			}
426			else
427			{
428				blocks_size = atoi(value_str.c_str());
429			}
430		}
431		else if (!strcmp(argv[arg], "--levels") || !strcmp(argv[arg], "-l"))
432		{
433			std::string value_str;
434			if ((arg + 1) < argc)
435			{
436				value_str = argv[arg+1];
437			}
438			if (((arg + 1) >= argc) || (value_str[0] == '-'))
439			{
440				std::cout << "No valid --levels argument given, default (5) will be used" << std::endl;
441			}
442			else
443			{
444				levels = atoi(value_str.c_str());
445			}
446		}
447		else if (!strcmp(argv[arg], "--reversible") || !strcmp(argv[arg], "-rev"))
448		{
449			reversible = true;
450		}
451		else if (!strcmp(argv[arg], "--logmetrics") || !strcmp(argv[arg], "-log"))
452		{
453			// '--logmetrics' needs to be specified with a named test metric argument
454			// Note: for the moment, only ImageCompressionTester has been tested
455			std::string test_name;
456			if ((arg + 1) < argc)
457			{
458				test_name = argv[arg+1];
459			}
460			if (((arg + 1) >= argc) || (test_name[0] == '-'))
461			{
462				// We don't have an argument left in the arg list or the next argument is another option
463				std::cout << "No --logmetrics argument given, no perf data will be gathered" << std::endl;
464			}
465			else
466			{
467				LLFastTimer::sMetricLog = TRUE;
468				LLFastTimer::sLogName = test_name;
469				arg += 1;					// Skip that arg now we know it's a valid test name
470				if ((arg + 1) == argc)		// Break out of the loop if we reach the end of the arg list
471					break;
472			}
473		}
474		else if (!strcmp(argv[arg], "--analyzeperformance") || !strcmp(argv[arg], "-a"))
475		{
476			analyze_performance = true;
477		}
478		else if (!strcmp(argv[arg], "--image-stats") || !strcmp(argv[arg], "-s"))
479		{
480			image_stats = true;
481		}
482	}
483		
484	// Check arguments consistency. Exit with proper message if inconsistent.
485	if (input_filenames.size() == 0)
486	{
487		std::cout << "No input file, nothing to do -> exit" << std::endl;
488		return 0;
489	}
490	if (analyze_performance && !LLFastTimer::sMetricLog)
491	{
492		std::cout << "Cannot create perf report if no perf gathered (i.e. use argument -log <perf> with -a) -> exit" << std::endl;
493		return 0;
494	}
495	
496
497	// Create the logging thread if required
498	if (LLFastTimer::sMetricLog)
499	{
500		LLFastTimer::sLogLock = new LLMutex(NULL);
501		fast_timer_log_thread = new LogThread(LLFastTimer::sLogName);
502		fast_timer_log_thread->start();
503	}
504	
505	// Perform action on each input file
506	std::list<std::string>::iterator in_file  = input_filenames.begin();
507	std::list<std::string>::iterator out_file = output_filenames.begin();
508	std::list<std::string>::iterator in_end = input_filenames.end();
509	std::list<std::string>::iterator out_end = output_filenames.end();
510	for (; in_file != in_end; ++in_file, ++out_file)
511	{
512		// Load file
513		LLPointer<LLImageRaw> raw_image = load_image(*in_file, discard_level, region, image_stats);
514		if (!raw_image)
515		{
516			std::cout << "Error: Image " << *in_file << " could not be loaded" << std::endl;
517			continue;
518		}
519	
520		// Save file
521		if (out_file != out_end)
522		{
523			if (!save_image(*out_file, raw_image, blocks_size, precincts_size, levels, reversible, image_stats))
524			{
525				std::cout << "Error: Image " << *out_file << " could not be saved" << std::endl;
526			}
527			else
528			{
529				std::cout << *in_file << " -> " << *out_file << std::endl;
530			}
531		}
532	}
533
534	// Output perf data if requested by user
535	if (analyze_performance)
536	{
537		std::string baseline_name = LLFastTimer::sLogName + "_baseline.slp";
538		std::string current_name  = LLFastTimer::sLogName + ".slp"; 
539		std::string report_name   = LLFastTimer::sLogName + "_report.csv";
540		
541		std::cout << "Analyzing performance, check report in : " << report_name << std::endl;
542
543		LLMetricPerformanceTesterBasic::doAnalysisMetrics(baseline_name, current_name, report_name);
544	}
545	
546	// Stop the perf gathering system if needed
547	if (LLFastTimer::sMetricLog)
548	{
549		LLMetricPerformanceTesterBasic::deleteTester(LLFastTimer::sLogName);
550		sAllDone = true;
551	}
552	
553	// Cleanup and exit
554	LLImage::cleanupClass();
555	if (fast_timer_log_thread)
556	{
557		fast_timer_log_thread->shutdown();
558	}
559	
560	return 0;
561}