#include #include #include #include #include #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" #include "aixlog.hpp" #include "util.h" #define PALETTE_ROWS 16 #define PALETTE_COLUMNS 16 #define PALETTE_BORDER 1 const uint MAX_FILESIZE = 2 * 1024 * 1024; // 2MB const uint MAX_STRING_LENGTH = 1024; //----------------- // C Strings const char* utilBaseName(const char* path_str) { assert(std::strlen(path_str) < MAX_STRING_LENGTH); const char* output = std::strrchr(path_str, '/'); if (output) return output; else return path_str; } bool utilCopyCStr(char* dest, const char* src, uint max_len) { assert(std::strlen(src) < MAX_STRING_LENGTH && max_len <= MAX_STRING_LENGTH); if (std::strlen(src) + 1 > max_len) return false; std::memcpy(dest, src, std::strlen(src) + 1); return true; } bool utilConcatPath(char* out, const char* base_dir, const char* file_name, uint max_len) { size_t l1 = std::strlen(base_dir); size_t l2 = std::strlen(file_name); size_t padded = l1 + l2 + 2; // NOTE: null term + '/' if (padded <= MAX_STRING_LENGTH && padded <= max_len) { int c = std::snprintf(out, padded, "%s/%s", base_dir, file_name); if (c > 0) return true; else return false; } return false; } bool utilMatchPrefix(const char* lhs, const char* rhs, int sz) { int rc = strncmp(lhs, rhs, sz); return (rc >= 0); } //----------------- // Memory allocation void * utilLogAlloc(uint item_count, uint type_size, const char* file_name, const int line) { assert(item_count > 0); // that was a fun bug void* mem = std::calloc(item_count, type_size); if (mem == nullptr) { LOG(ERROR) << "Memory allocation failed, called from " << file_name << ":" << line; } assert(mem != nullptr); // might as well stop execution here return mem; } void utilSafeFree(const void* mem) { if (mem != nullptr) { std::free((void *) mem); mem = nullptr; } } //----------------- // File I/O // TODO: don't use ftell() to get filesize // https://wiki.sei.cmu.edu/confluence/display/c/FIO19-C.+Do+not+use+fseek%28%29+and+ftell%28%29+to+compute+the+size+of+a+regular+file char * utilDumpTextFile(const char* filename) { LOG(INFO) << "loading filename, " << filename << "\n"; std::FILE* fp = std::fopen(filename, "rt"); assert(fp); std::fseek(fp, 0, SEEK_END); uint length = std::ftell(fp); std::fseek(fp, 0, SEEK_SET); assert(length < MAX_FILESIZE); // TODO: check error codes for fseek and ftell char* buf = UTIL_ALLOC(length, char); assert(buf); std::fread(buf, sizeof(char), length, fp); // TODO: check fp w/ ferror() here return buf; } // TODO: might want to do the base_dir concat in this function to prevent clobbering // user files on accident bool utilWriteTextFile(const char* filename, const char* text) { size_t text_len = std::strlen(text); if (text_len >= MAX_FILESIZE) { LOG(ERROR) << "that string is too big\n"; return false; } std::FILE* fp = fopen(filename, "wt"); if (fp) { size_t written = fwrite(text, sizeof(char), text_len, fp); fclose(fp); if (written == text_len) { LOG(DEBUG) << "successfuly wrote " << written << " bytes\n"; return true; } else { LOG(ERROR) << "error writing to file: " << filename << "\n"; return false; } } LOG(DEBUG) << text << "\n"; return false; } //----------------- // utilImage util_image utilLoadImage(const char* base_dir, const char* filename) { const uint max_path = 256; // NOTE: util_image.file_path is char[256] const char* base_name = utilBaseName(filename); // NOTE: +1 for '/' char, +1 for null uint l = std::strlen(base_dir) + std::strlen(base_name) + 2; assert(l < max_path); char full_path[l]; std::memcpy(full_path, base_dir, std::strlen(base_dir) + 1); std::strcat(std::strcat(full_path, "/"), base_name); LOG(INFO) << "Loading Image: " << full_path << "\n"; util_image image; std::memcpy(image.file_path, full_path, std::strlen(full_path) + 1); stbi_set_flip_vertically_on_load(1); image.pixels = stbi_load(full_path, &image.w, &image.h, &image.num_channels, 0); image.bits_per_channel = 8; image.data_len = image.w * image.h * image.num_channels; // NOTE: assumes 8 bits per channel if (image.pixels == 0) { LOG(ERROR) << stbi_failure_reason() << "\n"; utilFreeImage(image); return image; } LOG(INFO) << "Image properties, data_len: " << image.data_len << ", width: " << image.w << ", height: " << image.h << "\n"; return image; } void utilFreeImage(util_image image) { image.w = image.h = image.data_len = 0; image.bits_per_channel = 8; image.num_channels = 4; std::memset(image.file_path, 0, std::strlen(image.file_path) + 1); utilSafeFree(image.pixels); } // need to init default values from scene file, or finally make default config? v2f utilGetPaletteCoords(util_image& palette_image, uint color_index) { v2i tex_coords; v2f normalized; if (PALETTE_ROWS * PALETTE_COLUMNS < color_index) { LOG(ERROR) << "index out of range\n"; return normalized; } if (palette_image.pixels == nullptr) { LOG(ERROR) << "palatte image not loaded\n"; return normalized; } // NOTE: we're aiming to sample near the middle of the palette 'square' so GL_NEAREST // doesn't try to sample the border color v2i palette_square; palette_square.x = palette_image.w / PALETTE_ROWS; palette_square.y = palette_image.h / PALETTE_COLUMNS; uint v_row = (uint) std::floor(color_index / PALETTE_ROWS); uint u_column = color_index % PALETTE_ROWS; tex_coords.x = u_column * palette_square.x + palette_square.x / 2; tex_coords.y = v_row * palette_square.y + palette_square.y / 2; // NOTE: flip y coord tex_coords.y = palette_image.h - tex_coords.y; normalized.x = (float) tex_coords.x / palette_image.w; normalized.y = (float) tex_coords.y / palette_image.h; return normalized; }