summaryrefslogtreecommitdiff
path: root/indra/integration_tests/llimage_libtest/llimage_libtest.cpp
blob: 365f5f758cdb9c5547814fb57fa6b151863b043a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
/** 
 * @file llimage_libtest.cpp
 * @author Merov Linden
 * @brief Integration test for the llimage library
 *
 * $LicenseInfo:firstyear=2011&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2011, Linden Research, Inc.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */
#include "linden_common.h"
#include "llpointer.h"
#include "lltimer.h"

#include "llimage_libtest.h"

// Linden library includes
#include "llimage.h"
#include "llimagejpeg.h"
#include "llimagepng.h"
#include "llimagebmp.h"
#include "llimagetga.h"
#include "llimagej2c.h"
#include "lldir.h"

// system libraries
#include <iostream>

// doc string provided when invoking the program with --help 
static const char USAGE[] = "\n"
"usage:\tllimage_libtest [options]\n"
"\n"
" -h, --help\n"
"        Print this help\n"
" -i, --input <file1 .. file2>\n"
"        List of image files to load and convert. Patterns with wild cards can be used.\n"
" -o, --output <file1 .. file2> OR <type>\n"
"        List of image files to create (assumes same order as for input files)\n"
"        OR 3 letters file type extension to convert each input file into.\n"
" -log, --logmetrics <metric>\n"
"        Log performance data for <metric>. Results in <metric>.slp\n"
"        Note: so far, only ImageCompressionTester has been tested.\n"
" -r, --analyzeperformance\n"
"        Create a report comparing <metric>_baseline.slp with current <metric>.slp\n"
"        Results in <metric>_report.csv"
" -s, --image-stats\n"
"        Output stats for each input and output image.\n"
"\n";

// true when all image loading is done. Used by metric logging thread to know when to stop the thread.
static bool sAllDone = false;

// Create an empty formatted image instance of the correct type from the filename
LLPointer<LLImageFormatted> create_image(const std::string &filename)
{
	std::string exten = gDirUtilp->getExtension(filename);
	U32 codec = LLImageBase::getCodecFromExtension(exten);
	
	LLPointer<LLImageFormatted> image;
	switch (codec)
	{
		case IMG_CODEC_BMP:
			image = new LLImageBMP();
			break;
		case IMG_CODEC_TGA:
			image = new LLImageTGA();
			break;
		case IMG_CODEC_JPEG:
			image = new LLImageJPEG();
			break;
		case IMG_CODEC_J2C:
			image = new LLImageJ2C();
			break;
		case IMG_CODEC_PNG:
			image = new LLImagePNG();
			break;
		default:
			return NULL;
	}
	
	return image;
}

void output_image_stats(LLPointer<LLImageFormatted> image, const std::string &filename)
{
	// Print out some statistical data on the image
	std::cout << "Image stats for : " << filename << ", extension : " << image->getExtension() << std::endl;

	std::cout << "    with : " << (int)(image->getWidth())       << ", height : " << (int)(image->getHeight())       << std::endl;
	std::cout << "    comp : " << (int)(image->getComponents())  << ", levels : " << (int)(image->getDiscardLevel()) << std::endl;
	std::cout << "    head : " << (int)(image->calcHeaderSize()) << ",   data : " << (int)(image->getDataSize())     << std::endl;

	return;
}

// Load an image from file and return a raw (decompressed) instance of its data
LLPointer<LLImageRaw> load_image(const std::string &src_filename, bool output_stats)
{
	LLPointer<LLImageFormatted> image = create_image(src_filename);

	if (!image->load(src_filename))
	{
		return NULL;
	}
	
	if(	(image->getComponents() != 3) && (image->getComponents() != 4) )
	{
		std::cout << "Image files with less than 3 or more than 4 components are not supported\n";
		return NULL;
	}
	
	if (output_stats)
	{
		output_image_stats(image, src_filename);
	}
	
	LLPointer<LLImageRaw> raw_image = new LLImageRaw;
	if (!image->decode(raw_image, 0.0f))
	{
		return NULL;
	}
	
	return raw_image;
}

// Save a raw image instance into a file
bool save_image(const std::string &dest_filename, LLPointer<LLImageRaw> raw_image, bool output_stats)
{
	LLPointer<LLImageFormatted> image = create_image(dest_filename);
	
	if (!image->encode(raw_image, 0.0f))
	{
		return false;
	}
	
	if (output_stats)
	{
		output_image_stats(image, dest_filename);
	}

	return image->save(dest_filename);
}

void store_input_file(std::list<std::string> &input_filenames, const std::string &path)
{
	// Break the incoming path in its components
	std::string dir = gDirUtilp->getDirName(path);
	std::string name = gDirUtilp->getBaseFileName(path);
	std::string exten = gDirUtilp->getExtension(path);

	// std::cout << "store_input_file : " << path << ", dir : " << dir << ", name : " << name << ", exten : " << exten << std::endl;
	
	// If extension is not an image type or "*", exit
	// Note: we don't support complex patterns for the extension like "j??"
	// Note: on most shells, the pattern expansion is done by the shell so that pattern matching limitation is actually not a problem
	if ((exten.compare("*") != 0) && (LLImageBase::getCodecFromExtension(exten) == IMG_CODEC_INVALID))
	{
		return;
	}

	if ((name.find('*') != -1) || ((name.find('?') != -1)))
	{
		// If file name is a pattern, iterate to get each file name and store
		std::string next_name;
		while (gDirUtilp->getNextFileInDir(dir,name,next_name))
		{
			std::string file_name = dir + gDirUtilp->getDirDelimiter() + next_name;
			input_filenames.push_back(file_name);
		}
	}
	else
	{
		// Verify that the file does exist before storing 
		if (gDirUtilp->fileExists(path))
		{
			input_filenames.push_back(path);
		}
		else
		{
			std::cout << "store_input_file : the file " << path << " could not be found" << std::endl;
		}
	}	
}

void store_output_file(std::list<std::string> &output_filenames, std::list<std::string> &input_filenames, const std::string &path)
{
	// Break the incoming path in its components
	std::string dir = gDirUtilp->getDirName(path);
	std::string name = gDirUtilp->getBaseFileName(path);
	std::string exten = gDirUtilp->getExtension(path);
	
	// std::cout << "store_output_file : " << path << ", dir : " << dir << ", name : " << name << ", exten : " << exten << std::endl;
	
	if (dir.empty() && exten.empty())
	{
		// 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
		exten = name;
		// Make sure the extension is an image type
		if (LLImageBase::getCodecFromExtension(exten) == IMG_CODEC_INVALID)
		{
			return;
		}
		std::string delim = gDirUtilp->getDirDelimiter();
		std::list<std::string>::iterator in_file  = input_filenames.begin();
		std::list<std::string>::iterator end = input_filenames.end();
		for (; in_file != end; ++in_file)
		{
			dir = gDirUtilp->getDirName(*in_file);
			name = gDirUtilp->getBaseFileName(*in_file,true);
			std::string file_name;
			if (!dir.empty())
			{
				file_name = dir + delim + name + "." + exten;
			}
			else
			{
				file_name = name + "." + exten;
			}
			output_filenames.push_back(file_name);
		}
	}
	else
	{
		// Make sure the extension is an image type
		if (LLImageBase::getCodecFromExtension(exten) == IMG_CODEC_INVALID)
		{
			return;
		}
		// Store the path
		output_filenames.push_back(path);
	}
}

// Holds the metric gathering output in a thread safe way
class LogThread : public LLThread
{
public:
	std::string mFile;

	LogThread(std::string& test_name) : LLThread("llimage_libtest log")
	{
		std::string file_name = test_name + std::string(".slp");
		mFile = file_name;
	}
		
	void run()
	{
		std::ofstream os(mFile.c_str());
			
		while (!sAllDone)
		{
			LLFastTimer::writeLog(os);
			os.flush();
			ms_sleep(32);
		}
		LLFastTimer::writeLog(os);
		os.flush();
		os.close();
	}		
};

int main(int argc, char** argv)
{
	// List of input and output files
	std::list<std::string> input_filenames;
	std::list<std::string> output_filenames;
	bool analyze_performance = false;
	bool image_stats = false;

	// Init whatever is necessary
	ll_init_apr();
	LLImage::initClass();
	LogThread* fast_timer_log_thread = NULL;	// For performance and metric gathering

	// Analyze command line arguments
	for (int arg = 1; arg < argc; ++arg)
	{
		if (!strcmp(argv[arg], "--help") || !strcmp(argv[arg], "-h"))
		{
            // Send the usage to standard out
            std::cout << USAGE << std::endl;
			return 0;
		}
		else if ((!strcmp(argv[arg], "--input") || !strcmp(argv[arg], "-i")) && arg < argc-1)
		{
			std::string file_name = argv[arg+1];
			while (file_name[0] != '-')		// if arg starts with '-', we consider it's not a file name but some other argument
			{
				// std::cout << "input file name : " << file_name << std::endl;				
				store_input_file(input_filenames, file_name);
				arg += 1;					// Skip that arg now we know it's a file name
				if ((arg + 1) == argc)		// Break out of the loop if we reach the end of the arg list
					break;
				file_name = argv[arg+1];	// Next argument and loop over
			}
		}
		else if ((!strcmp(argv[arg], "--output") || !strcmp(argv[arg], "-o")) && arg < argc-1)
		{
			std::string file_name = argv[arg+1];
			while (file_name[0] != '-')		// if arg starts with '-', we consider it's not a file name but some other argument
			{
				// std::cout << "output file name : " << file_name << std::endl;				
				store_output_file(output_filenames, input_filenames, file_name);
				arg += 1;					// Skip that arg now we know it's a file name
				if ((arg + 1) == argc)		// Break out of the loop if we reach the end of the arg list
					break;
				file_name = argv[arg+1];	// Next argument and loop over
			}
		}
		else if (!strcmp(argv[arg], "--logmetrics") || !strcmp(argv[arg], "-log"))
		{
			// '--logmetrics' needs to be specified with a named test metric argument
			// Note: for the moment, only ImageCompressionTester has been tested
			std::string test_name;
			if ((arg + 1) < argc)
			{
				test_name = argv[arg+1];
			}
			if (((arg + 1) >= argc) || (test_name[0] == '-'))
			{
				// We don't have an argument left in the arg list or the next argument is another option
				std::cout << "No --logmetrics argument given, no perf data will be gathered" << std::endl;
			}
			else
			{
				LLFastTimer::sMetricLog = TRUE;
				LLFastTimer::sLogName = test_name;
				arg += 1;					// Skip that arg now we know it's a valid test name
				if ((arg + 1) == argc)		// Break out of the loop if we reach the end of the arg list
					break;
			}
		}
		else if (!strcmp(argv[arg], "--analyzeperformance") || !strcmp(argv[arg], "-r"))
		{
			analyze_performance = true;
		}
		else if (!strcmp(argv[arg], "--image-stats") || !strcmp(argv[arg], "-s"))
		{
			image_stats = true;
		}
	}
		
	// Check arguments consistency. Exit with proper message if inconsistent.
	if (input_filenames.size() == 0)
	{
		std::cout << "No input file, nothing to do -> exit" << std::endl;
		return 0;
	}
	if (analyze_performance && !LLFastTimer::sMetricLog)
	{
		std::cout << "Cannot create perf report if no perf gathered (i.e. use argument -log <perf> with -r) -> exit" << std::endl;
		return 0;
	}
	

	// Create the logging thread if required
	if (LLFastTimer::sMetricLog)
	{
		LLFastTimer::sLogLock = new LLMutex(NULL);
		fast_timer_log_thread = new LogThread(LLFastTimer::sLogName);
		fast_timer_log_thread->start();
	}
	
	// Perform action on each input file
	std::list<std::string>::iterator in_file  = input_filenames.begin();
	std::list<std::string>::iterator out_file = output_filenames.begin();
	std::list<std::string>::iterator in_end = input_filenames.end();
	std::list<std::string>::iterator out_end = output_filenames.end();
	for (; in_file != in_end; ++in_file)
	{
		// Load file
		LLPointer<LLImageRaw> raw_image = load_image(*in_file, image_stats);
		if (!raw_image)
		{
			std::cout << "Error: Image " << *in_file << " could not be loaded" << std::endl;
			continue;
		}
	
		// Save file
		if (out_file != out_end)
		{
			if (!save_image(*out_file, raw_image, image_stats))
			{
				std::cout << "Error: Image " << *out_file << " could not be saved" << std::endl;
			}
			else
			{
				std::cout << *in_file << " -> " << *out_file << std::endl;
			}
			++out_file;
		}
	}

	// Stop the perf gathering system if needed
	if (LLFastTimer::sMetricLog)
	{
		LLMetricPerformanceTesterBasic::deleteTester(LLFastTimer::sLogName);
		sAllDone = true;
	}
	
	// Output perf data if requested by user
	if (analyze_performance)
	{
		std::cout << "Analyzing performance" << std::endl;
		
		std::string baseline_name = LLFastTimer::sLogName + "_baseline.slp";
		std::string current_name  = LLFastTimer::sLogName + ".slp"; 
		std::string report_name   = LLFastTimer::sLogName + "_report.csv";
		
		LLMetricPerformanceTesterBasic::doAnalysisMetrics(baseline_name, current_name, report_name);
	}
	
	// Cleanup and exit
	LLImage::cleanupClass();
	if (fast_timer_log_thread)
	{
		fast_timer_log_thread->shutdown();
	}
	
	return 0;
}