brewery
notashelf /
46ea9402424d58c5b637008292a5c379e05f9c7b

chroma

public

Lightweight wallpaper daemon for Wayland

Star0tarball
clone
ssh://git@git.notashelf.dev:33/notashelf/chroma.git

Commit 46ea9402424d

tarball

NotAShelf <raf@notashelf.dev> · 2026-01-31 12:15 UTC

unverified1 files changed+147-7
image: initial downsampling implementation

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Icec8c434ecf480c644a6f6e6a3b8cd5b6a6a6964
diff --git a/src/image.c b/src/image.cindex 6a22991..caea5e2 100644--- a/src/image.c+++ b/src/image.c@@ -24,8 +24,76 @@ static long get_file_size(const char *path) {   return st.st_size; } -// Load image from file-int chroma_image_load(chroma_image_t *image, const char *path) {+// Calculate optimal image size based on output dimensions+static void calculate_optimal_size(int original_width, int original_height,+                                   int max_output_width, int max_output_height,+                                   int *optimal_width, int *optimal_height) {+  // If image is smaller than outputs, keep original size+  if (original_width <= max_output_width &&+      original_height <= max_output_height) {+    *optimal_width = original_width;+    *optimal_height = original_height;+    return;+  }++  // Calculate scale factor to fit within max output dimensions+  float scale_x = (float)max_output_width / original_width;+  float scale_y = (float)max_output_height / original_height;+  float scale = (scale_x < scale_y) ? scale_x : scale_y;++  // Apply scale factor with minimum size to avoid too small images+  scale = (scale > 1.0f) ? 1.0f : scale;+  scale = (scale < 0.25f) ? 0.25f : scale; // XXX: don't scale below 25%++  *optimal_width = (int)(original_width * scale);+  *optimal_height = (int)(original_height * scale);++  // Ensure even dimensions for better GPU alignment+  *optimal_width = (*optimal_width / 2) * 2;+  *optimal_height = (*optimal_height / 2) * 2;+}++// FIXME: this is a very simple way of implementing box filter downsampling for+// memory efficiency Could be better, but this is good enough *for the time+// being*. Must be revisited in the future to see how it stands as the program+// evolves.+static int downsample_image(unsigned char *src_data, int src_width,+                            int src_height, unsigned char *dst_data,+                            int dst_width, int dst_height) {+  if (!src_data || !dst_data) {+    return -1;+  }++  float x_ratio = (float)src_width / dst_width;+  float y_ratio = (float)src_height / dst_height;++  for (int y = 0; y < dst_height; y++) {+    for (int x = 0; x < dst_width; x++) {+      // Calculate corresponding source pixel+      int src_x = (int)(x * x_ratio);+      int src_y = (int)(y * y_ratio);++      // Ensure we're within bounds+      src_x = (src_x >= src_width) ? src_width - 1 : src_x;+      src_y = (src_y >= src_height) ? src_height - 1 : src_y;++      // Copy pixel (RGBA)+      int src_idx = (src_y * src_width + src_x) * 4;+      int dst_idx = (y * dst_width + x) * 4;++      dst_data[dst_idx + 0] = src_data[src_idx + 0]; // R+      dst_data[dst_idx + 1] = src_data[src_idx + 1]; // G+      dst_data[dst_idx + 2] = src_data[src_idx + 2]; // B+      dst_data[dst_idx + 3] = src_data[src_idx + 3]; // A+    }+  }++  return 0;+}++// Load image from file with configurable downsampling+int chroma_image_load(chroma_image_t *image, const char *path,+                      const chroma_config_t *config) {   if (!image || !path) {     chroma_log("ERROR", "Invalid parameters for image loading");     return CHROMA_ERROR_INIT;@@ -77,15 +145,87 @@ int chroma_image_load(chroma_image_t *image, const char *path) {     return CHROMA_ERROR_IMAGE;   } +  // Store original dimensions before potential downsampling+  int original_width = image->width;+  int original_height = image->height;++  // Apply intelligent downsampling if enabled+  bool should_downsample = false;+  int optimal_width = original_width;+  int optimal_height = original_height;++  if (config && config->enable_downsampling) {+    calculate_optimal_size(original_width, original_height,+                           config->max_output_width, config->max_output_height,+                           &optimal_width, &optimal_height);++    // Apply minimum scale factor constraint+    float scale_x = (float)optimal_width / original_width;+    float scale_y = (float)optimal_height / original_height;+    float scale = (scale_x < scale_y) ? scale_x : scale_y;++    if (scale < config->min_scale_factor) {+      scale = config->min_scale_factor;+      optimal_width = (int)(original_width * scale);+      optimal_height = (int)(original_height * scale);++      // Ensure even dimensions+      optimal_width = (optimal_width / 2) * 2;+      optimal_height = (optimal_height / 2) * 2;+    }++    should_downsample =+        (optimal_width < original_width || optimal_height < original_height);+  }++  // Downsamp if needed and enabled+  if (should_downsample) {+    chroma_log("INFO",+               "Downsampling image from %dx%d to %dx%d (%.1f%% of original)",+               original_width, original_height, optimal_width, optimal_height,+               (float)(optimal_width * optimal_height) /+                   (original_width * original_height) * 100.0f);++    size_t optimal_size = (size_t)optimal_width * optimal_height * 4;+    unsigned char *downsampled_data = malloc(optimal_size);+    if (!downsampled_data) {+      chroma_log("ERROR", "Failed to allocate memory for downsampled image");+      chroma_image_free(image);+      return CHROMA_ERROR_MEMORY;+    }++    if (downsample_image(image->data, original_width, original_height,+                         downsampled_data, optimal_width,+                         optimal_height) != 0) {+      chroma_log("ERROR", "Failed to downsample image");+      free(downsampled_data);+      chroma_image_free(image);+      return CHROMA_ERROR_IMAGE;+    }++    stbi_image_free(image->data);+    image->data = downsampled_data;+    image->width = optimal_width;+    image->height = optimal_height;++    chroma_log("DEBUG", "Successfully downsampled image to %dx%d",+               optimal_width, optimal_height);+  } else if (config && !config->enable_downsampling) {+    chroma_log("DEBUG",+               "Downsampling disabled, keeping original resolution %dx%d",+               original_width, original_height);+  }+   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,+  chroma_log("INFO", "Loaded image: %s (%dx%d, %d channels, %.2f MB)%s", path,              image->width, image->height, image->channels,-             (double)image_size / (1024.0 * 1024.0));+             (double)image_size / (1024.0 * 1024.0),+             should_downsample ? " (downsampled)" : "");    return CHROMA_OK; }@@ -159,8 +299,8 @@ chroma_image_t *chroma_image_get_or_load(chroma_state_t *state,     state->image_count++;   } -  // Load the image-  if (chroma_image_load(image, path) != CHROMA_OK) {+  // Load the image with configuration+  if (chroma_image_load(image, path, &state->config) != CHROMA_OK) {     // If this was a new slot, decrement count     if (!existing) {       state->image_count--;@@ -241,7 +381,7 @@ void chroma_image_init_stb(void) {   // Set stb_image options   stbi_set_flip_vertically_on_load(0); -  // These could be made configurable+  // FIXME: these could be made configurable   stbi_ldr_to_hdr_gamma(2.2f);   stbi_ldr_to_hdr_scale(1.0f);