/** * @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 // 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 \n" " List of image files to load and convert. Patterns with wild cards can be used.\n" " -o, --output OR \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 \n" " Log performance data for . Results in .slp\n" " Note: so far, only ImageCompressionTester has been tested.\n" " -r, --analyzeperformance\n" " Create a report comparing _baseline.slp with current .slp\n" " Results in _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 create_image(const std::string &filename) { std::string exten = gDirUtilp->getExtension(filename); U32 codec = LLImageBase::getCodecFromExtension(exten); LLPointer 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 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 load_image(const std::string &src_filename, bool output_stats) { LLPointer 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 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 raw_image, bool output_stats) { LLPointer 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 &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 &output_filenames, std::list &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::iterator in_file = input_filenames.begin(); std::list::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 input_filenames; std::list 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 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::iterator in_file = input_filenames.begin(); std::list::iterator out_file = output_filenames.begin(); std::list::iterator in_end = input_filenames.end(); std::list::iterator out_end = output_filenames.end(); for (; in_file != in_end; ++in_file) { // Load file LLPointer 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; }