#define STB_IMAGE_IMPLEMENTATION #include "../include/stb_image.h" #include #include #include #include #include #include "../include/chroma.h" // Check if file exists and is readable static int file_exists(const char *path) { struct stat st; return (stat(path, &st) == 0 && S_ISREG(st.st_mode)); } // Get file size static long get_file_size(const char *path) { struct stat st; if (stat(path, &st) != 0) { return -1; } return st.st_size; } // Load image from file int chroma_image_load(chroma_image_t *image, const char *path) { if (!image || !path) { chroma_log("ERROR", "Invalid parameters for image loading"); return CHROMA_ERROR_INIT; } // Initialize image structure memset(image, 0, sizeof(chroma_image_t)); strncpy(image->path, path, MAX_PATH_LEN - 1); image->path[MAX_PATH_LEN - 1] = '\0'; // Check if file exists if (!file_exists(path)) { chroma_log("ERROR", "Image file does not exist: %s", path); return CHROMA_ERROR_IMAGE; } // Get file size for logging long file_size = get_file_size(path); if (file_size > 0) { chroma_log("DEBUG", "Loading image: %s (%.2f MB)", path, (double)file_size / (1024.0 * 1024.0)); } // Load image data using stb_image, force RGBA format to avoid conversion stbi_set_flip_vertically_on_load(0); // keep images right-side up image->data = stbi_load(path, &image->width, &image->height, &image->channels, 4); image->channels = 4; // always RGBA after forced conversion if (!image->data) { chroma_log("ERROR", "Failed to load image %s: %s", path, stbi_failure_reason()); return CHROMA_ERROR_IMAGE; } // Validate image dimensions if (image->width <= 0 || image->height <= 0) { chroma_log("ERROR", "Invalid image dimensions: %dx%d", image->width, image->height); chroma_image_free(image); return CHROMA_ERROR_IMAGE; } // Validate we have RGBA data (should always be true with forced conversion) if (image->channels != 4) { chroma_log("ERROR", "Failed to load image as RGBA: got %d channels", image->channels); chroma_image_free(image); return CHROMA_ERROR_IMAGE; } image->loaded = true; // Calculate and log memory allocation size_t image_size = (size_t)image->width * image->height * image->channels; chroma_log_resource_allocation("image_data", image_size, path); chroma_log("INFO", "Loaded image: %s (%dx%d, %d channels, %.2f MB)", path, image->width, image->height, image->channels, (double)image_size / (1024.0 * 1024.0)); return CHROMA_OK; } // Free image data void chroma_image_free(chroma_image_t *image) { if (!image) { return; } if (image->data) { // Log memory deallocation before freeing size_t image_size = (size_t)image->width * image->height * image->channels; chroma_log_resource_deallocation("image_data", image_size, image->path); // Always use stbi_image_free since we load directly with stbi_load stbi_image_free(image->data); image->data = NULL; } image->width = 0; image->height = 0; image->channels = 0; image->loaded = false; if (strlen(image->path) > 0) { chroma_log("DEBUG", "Freed image: %s", image->path); } memset(image->path, 0, sizeof(image->path)); } // Find image by path in state chroma_image_t *chroma_image_find_by_path(chroma_state_t *state, const char *path) { if (!state || !path) { return NULL; } for (int i = 0; i < state->image_count; i++) { if (strcmp(state->images[i].path, path) == 0) { return &state->images[i]; } } return NULL; } // Load image if not already loaded chroma_image_t *chroma_image_get_or_load(chroma_state_t *state, const char *path) { if (!state || !path) { return NULL; } // Check if already loaded chroma_image_t *existing = chroma_image_find_by_path(state, path); if (existing && existing->loaded) { chroma_log("DEBUG", "Using cached image: %s", path); return existing; } // Find empty slot or reuse existing chroma_image_t *image = existing; if (!image) { if (state->image_count >= MAX_OUTPUTS) { chroma_log("ERROR", "Maximum number of images reached"); return NULL; } image = &state->images[state->image_count]; state->image_count++; } // Load the image if (chroma_image_load(image, path) != CHROMA_OK) { // If this was a new slot, decrement count if (!existing) { state->image_count--; } return NULL; } return image; } // Validate image file format int chroma_image_validate(const char *path) { if (!path || !file_exists(path)) { return CHROMA_ERROR_IMAGE; } // Check file extension (basic validation) const char *ext = strrchr(path, '.'); if (!ext) { return CHROMA_ERROR_IMAGE; } ext++; // Skip the dot // Check supported extensions if (strcasecmp(ext, "jpg") == 0 || strcasecmp(ext, "jpeg") == 0 || strcasecmp(ext, "png") == 0 || strcasecmp(ext, "bmp") == 0 || strcasecmp(ext, "tga") == 0 || strcasecmp(ext, "psd") == 0 || strcasecmp(ext, "gif") == 0 || strcasecmp(ext, "hdr") == 0 || strcasecmp(ext, "pic") == 0 || strcasecmp(ext, "ppm") == 0 || strcasecmp(ext, "pgm") == 0) { return CHROMA_OK; } chroma_log("WARN", "Potentially unsupported image format: %s", ext); return CHROMA_ERROR_IMAGE; } // Get image info without loading full data int chroma_image_get_info(const char *path, int *width, int *height, int *channels) { if (!path || !width || !height || !channels) { return CHROMA_ERROR_INIT; } if (!file_exists(path)) { return CHROMA_ERROR_IMAGE; } if (!stbi_info(path, width, height, channels)) { chroma_log("ERROR", "Failed to get image info for %s: %s", path, stbi_failure_reason()); return CHROMA_ERROR_IMAGE; } return CHROMA_OK; } // Cleanup all images in state void chroma_images_cleanup(chroma_state_t *state) { if (!state) { return; } chroma_log("DEBUG", "Cleaning up %d images", state->image_count); for (int i = 0; i < state->image_count; i++) { chroma_image_free(&state->images[i]); } state->image_count = 0; chroma_log("INFO", "Cleaned up all images"); chroma_log_memory_stats("post-image-cleanup"); } // Preload common image formats for validation void chroma_image_init_stb(void) { // Set stb_image options stbi_set_flip_vertically_on_load(0); // These could be made configurable stbi_ldr_to_hdr_gamma(2.2f); stbi_ldr_to_hdr_scale(1.0f); chroma_log("DEBUG", "Initialized stb_image library"); }