// File: corpus_gen.cpp - Block compression corpus generator. // See Copyright Notice and license at the end of inc/crnlib.h // // Example command line: // -gentest [-deep] [-blockpercentage .035] [-width 4096] [-height 4096] -in c:\temp\*.jpg [-in c:\temp\*.jpeg] [-in @blah.txt] #include "crn_core.h" #include "corpus_gen.h" #include "crn_console.h" #include "crn_find_files.h" #include "crn_file_utils.h" #include "crn_command_line_params.h" #include "crn_dxt.h" #include "crn_cfile_stream.h" #include "crn_texture_conversion.h" #include "crn_radix_sort.h" #define CRND_HEADER_FILE_ONLY #include "crn_decomp.h" namespace crnlib { struct block { color_quad_u8 m_c[4*4]; inline operator size_t() const { return fast_hash(this, sizeof(*this)); } inline bool operator== (const block& rhs) const { return memcmp(this, &rhs, sizeof(*this)) == 0; } }; typedef crnlib::hash_map block_hash_map; corpus_gen::corpus_gen() { } void corpus_gen::sort_blocks(image_u8& img) { const uint num_blocks_x = img.get_width() / 4; const uint num_blocks_y = img.get_height() / 4; const uint total_blocks = num_blocks_x * num_blocks_y; console::printf("Sorting %u blocks...", total_blocks); crnlib::vector block_std_dev(total_blocks); for (uint by = 0; by < num_blocks_y; by++) { for (uint bx = 0; bx < num_blocks_x; bx++) { color_quad_u8 c[4 * 4]; for (uint y = 0; y < 4; y++) for (uint x = 0; x < 4; x++) c[x+y*4] = img(bx*4+x, by*4+y); double std_dev = 0.0f; for (uint i = 0; i < 3; i++) std_dev += image_utils::compute_std_dev(16, c, i, 1); block_std_dev[bx + by * num_blocks_x] = (float)std_dev; } } crnlib::vector block_indices0(total_blocks); crnlib::vector block_indices1(total_blocks); const uint* pIndices = indirect_radix_sort(total_blocks, &block_indices0[0], &block_indices1[0], &block_std_dev[0], 0, sizeof(float), true); image_u8 new_img(img.get_width(), img.get_height()); uint dst_block_index = 0; //float prev_std_dev = -999; for (uint i = 0; i < total_blocks; i++) { uint src_block_index = pIndices[i]; //float std_dev = block_std_dev[src_block_index]; //crnlib_ASSERT(std_dev >= prev_std_dev); //prev_std_dev = std_dev; uint src_block_x = src_block_index % num_blocks_x; uint src_block_y = src_block_index / num_blocks_x; uint dst_block_x = dst_block_index % num_blocks_x; uint dst_block_y = dst_block_index / num_blocks_x; new_img.unclipped_blit(src_block_x * 4, src_block_y * 4, 4, 4, dst_block_x * 4, dst_block_y * 4, img); dst_block_index++; } #if 0 //new_img.swap(img); #else crnlib::vector remaining_blocks(num_blocks_x); console::printf("Arranging %u blocks...", total_blocks); for (uint by = 0; by < num_blocks_y; by++) { console::printf("%u of %u", by, num_blocks_y); remaining_blocks.resize(num_blocks_x); for (uint i = 0; i < num_blocks_x; i++) remaining_blocks[i] = i; color_quad_u8 match_block[16]; utils::zero_object(match_block); for (uint bx = 0; bx < num_blocks_x; bx++) { uint best_index = 0; uint64 best_error = cUINT64_MAX; for (uint i = 0; i < remaining_blocks.size(); i++) { uint src_block_index = remaining_blocks[i]; uint64 error = 0; for (uint y = 0; y < 4; y++) { for (uint x = 0; x < 4; x++) { const color_quad_u8& c = new_img(src_block_index*4+x, by*4+y); error += color::elucidian_distance(c, match_block[x+y*4], false); } } if (error < best_error) { best_error = error; best_index = i; } } uint src_block_index = remaining_blocks[best_index]; for (uint y = 0; y < 4; y++) { for (uint x = 0; x < 4; x++) { const color_quad_u8& c = new_img(src_block_index*4+x, by*4+y); match_block[x+y*4] = c; img(bx * 4 + x, by * 4 + y) = c; } } remaining_blocks.erase_unordered(best_index); } } #endif } bool corpus_gen::generate(const char* pCmd_line) { static const command_line_params::param_desc param_desc_array[] = { { "corpus_gen", 0, false }, { "in", 1, true }, { "deep", 0, false }, { "blockpercentage", 1, false }, { "width", 1, false }, { "height", 1, false }, { "alpha", 0, false }, }; command_line_params params; if (!params.parse(pCmd_line, CRNLIB_ARRAY_SIZE(param_desc_array), param_desc_array, true)) return false; if (!params.has_key("in")) { console::error("Must specify one or more input files using the /in option!"); return false; } uint num_dst_blocks_x = params.get_value_as_int("width", 0, 4096, 128, 4096); num_dst_blocks_x = (num_dst_blocks_x + 3) / 4; uint num_dst_blocks_y = params.get_value_as_int("height", 0, 4096, 128, 4096); num_dst_blocks_y = (num_dst_blocks_y + 3) / 4; const uint total_dst_blocks = num_dst_blocks_x * num_dst_blocks_y; image_u8 dst_img(num_dst_blocks_x * 4, num_dst_blocks_y * 4); uint next_dst_block = 0; uint total_dst_images = 0; random rm; block_hash_map block_hash; block_hash.reserve(total_dst_blocks); uint total_images_loaded = 0; uint total_blocks_written = 0; command_line_params::param_map_const_iterator it = params.begin(); for ( ; it != params.end(); ++it) { if (it->first != "in") continue; if (it->second.m_values.empty()) { console::error("Must follow /in parameter with a filename!\n"); return false; } for (uint in_value_index = 0; in_value_index < it->second.m_values.size(); in_value_index++) { const dynamic_string& filespec = it->second.m_values[in_value_index]; find_files file_finder; if (!file_finder.find(filespec.get_ptr(), find_files::cFlagAllowFiles | (params.has_key("deep") ? find_files::cFlagRecursive : 0))) { console::warning("Failed finding files: %s", filespec.get_ptr()); continue; } if (file_finder.get_files().empty()) { console::warning("No files found: %s", filespec.get_ptr()); return false; } const find_files::file_desc_vec& files = file_finder.get_files(); for (uint file_index = 0; file_index < files.size(); file_index++) { const find_files::file_desc& file_desc = files[file_index]; console::printf("Loading image: %s", file_desc.m_fullname.get_ptr()); image_u8 img; if (!image_utils::read_from_file(img, file_desc.m_fullname.get_ptr(), 0)) { console::warning("Failed loading image file: %s", file_desc.m_fullname.get_ptr()); continue; } if (!params.has_key("alpha")) { for (uint y = 0; y < img.get_height(); y++) for (uint x = 0; x < img.get_width(); x++) img(x, y).a = 255; } total_images_loaded++; uint width = img.get_width(); uint height = img.get_height(); uint num_blocks_x = (width + 3) / 4; uint num_blocks_y = (height + 3) / 4; uint total_blocks = num_blocks_x * num_blocks_y; float percentage = params.get_value_as_float("blockpercentage", 0, .1f, .001f, 1.0f); uint total_rand_blocks = math::maximum(1U, (uint)(total_blocks * percentage)); crnlib::vector remaining_blocks(total_blocks); for (uint i = 0; i < total_blocks; i++) remaining_blocks[i] = i; uint num_blocks_remaining = total_rand_blocks; while (num_blocks_remaining) { if (remaining_blocks.empty()) break; uint rand_block_index = rm.irand(0, remaining_blocks.size()); uint block_index = remaining_blocks[rand_block_index]; remaining_blocks.erase_unordered(rand_block_index); uint block_y = block_index / num_blocks_x; uint block_x = block_index % num_blocks_x; block b; for (uint y = 0; y < 4; y++) { for (uint x = 0; x < 4; x++) { b.m_c[x+y*4] = img.get_clamped(block_x*4+x, block_y*4+y); } } if (!block_hash.insert(b).second) continue; if (block_hash.size() == total_dst_blocks) { block_hash.clear(); block_hash.reserve(total_dst_blocks); } uint dst_block_x = next_dst_block % num_dst_blocks_x; uint dst_block_y = next_dst_block / num_dst_blocks_x; for (uint y = 0; y < 4; y++) { for (uint x = 0; x < 4; x++) { dst_img(dst_block_x * 4 + x, dst_block_y * 4 + y) = b.m_c[x + y*4]; } } next_dst_block++; if (total_dst_blocks == next_dst_block) { sort_blocks(dst_img); dynamic_string dst_filename(cVarArg, "test_%u.tga", total_dst_images); console::printf("Writing image: %s", dst_filename.get_ptr()); image_utils::write_to_file(dst_filename.get_ptr(), dst_img, 0); dst_img.set_all(color_quad_u8::make_black()); next_dst_block = 0; total_dst_images++; } total_blocks_written++; num_blocks_remaining--; } } // file_index } // in_value_index } if (next_dst_block) { sort_blocks(dst_img); dynamic_string dst_filename(cVarArg, "test_%u.tga", total_dst_images); console::printf("Writing image: %s", dst_filename.get_ptr()); image_utils::write_to_file(dst_filename.get_ptr(), dst_img, 0); next_dst_block = 0; total_dst_images++; } console::printf("Found %u input images, %u output images, %u total blocks", total_images_loaded, total_dst_images, total_blocks_written); return true; } } // namespace crnlib