diff --git a/include/chroma.h b/include/chroma.h index 6c8ed6e..76afc43 100644 --- a/include/chroma.h +++ b/include/chroma.h @@ -44,6 +44,19 @@ typedef enum { CHROMA_SCALE_CENTER = 3 // center image at original size } chroma_scale_mode_t; +// Anchor positions for wallpaper placement +typedef enum { + CHROMA_ANCHOR_CENTER = 0, // center of the output (default) + CHROMA_ANCHOR_TOP = 1, // top edge, centered horizontally + CHROMA_ANCHOR_BOTTOM = 2, // bottom edge, centered horizontally + CHROMA_ANCHOR_LEFT = 3, // left edge, centered vertically + CHROMA_ANCHOR_RIGHT = 4, // right edge, centered vertically + CHROMA_ANCHOR_TOP_LEFT = 5, // top-left corner + CHROMA_ANCHOR_TOP_RIGHT = 6, // top-right corner + CHROMA_ANCHOR_BOTTOM_LEFT = 7, // bottom-left corner + CHROMA_ANCHOR_BOTTOM_RIGHT = 8 // bottom-right corner +} chroma_anchor_t; + // Image filtering quality settings typedef enum { CHROMA_FILTER_NEAREST = 0, // nearest neighbor filtering (pixelated) @@ -90,6 +103,7 @@ typedef struct { // Configuration for this output chroma_scale_mode_t scale_mode; chroma_filter_quality_t filter_quality; + chroma_anchor_t anchor; bool config_loaded; // OpenGL resource cache @@ -108,6 +122,7 @@ typedef struct { char image_path[MAX_PATH_LEN]; chroma_scale_mode_t scale_mode; chroma_filter_quality_t filter_quality; + chroma_anchor_t anchor; } chroma_config_mapping_t; // Application configuration @@ -120,6 +135,7 @@ typedef struct { // Global scaling and filtering settings (used as defaults) chroma_scale_mode_t default_scale_mode; chroma_filter_quality_t default_filter_quality; + chroma_anchor_t default_anchor; // Image downsampling settings bool enable_downsampling; // enable automatic downsampling @@ -231,7 +247,8 @@ const char *chroma_config_get_image_for_output(chroma_config_t *config, const char *output_name); int chroma_config_get_mapping_for_output( chroma_config_t *config, const char *output_name, - chroma_scale_mode_t *scale_mode, chroma_filter_quality_t *filter_quality); + chroma_scale_mode_t *scale_mode, chroma_filter_quality_t *filter_quality, + chroma_anchor_t *anchor); void chroma_config_print(const chroma_config_t *config); diff --git a/src/config.c b/src/config.c index 619af3a..0a35467 100644 --- a/src/config.c +++ b/src/config.c @@ -6,8 +6,6 @@ #include "../include/chroma.h" -// Default configuration values - static char *trim_whitespace(char *str) { char *end; @@ -122,11 +120,71 @@ static const char *filter_quality_to_string(chroma_filter_quality_t quality) { } } +// Parse anchor position from string +static chroma_anchor_t parse_anchor(const char *value) { + if (!value) + return CHROMA_ANCHOR_CENTER; + + if (strcasecmp(value, "center") == 0) { + return CHROMA_ANCHOR_CENTER; + } else if (strcasecmp(value, "top") == 0) { + return CHROMA_ANCHOR_TOP; + } else if (strcasecmp(value, "bottom") == 0) { + return CHROMA_ANCHOR_BOTTOM; + } else if (strcasecmp(value, "left") == 0) { + return CHROMA_ANCHOR_LEFT; + } else if (strcasecmp(value, "right") == 0) { + return CHROMA_ANCHOR_RIGHT; + } else if (strcasecmp(value, "top-left") == 0 || + strcasecmp(value, "topleft") == 0) { + return CHROMA_ANCHOR_TOP_LEFT; + } else if (strcasecmp(value, "top-right") == 0 || + strcasecmp(value, "topright") == 0) { + return CHROMA_ANCHOR_TOP_RIGHT; + } else if (strcasecmp(value, "bottom-left") == 0 || + strcasecmp(value, "bottomleft") == 0) { + return CHROMA_ANCHOR_BOTTOM_LEFT; + } else if (strcasecmp(value, "bottom-right") == 0 || + strcasecmp(value, "bottomright") == 0) { + return CHROMA_ANCHOR_BOTTOM_RIGHT; + } + + chroma_log("WARN", "Unknown anchor: %s (using center)", value); + return CHROMA_ANCHOR_CENTER; +} + +// Get string representation of anchor position +static const char *anchor_to_string(chroma_anchor_t anchor) { + switch (anchor) { + case CHROMA_ANCHOR_CENTER: + return "center"; + case CHROMA_ANCHOR_TOP: + return "top"; + case CHROMA_ANCHOR_BOTTOM: + return "bottom"; + case CHROMA_ANCHOR_LEFT: + return "left"; + case CHROMA_ANCHOR_RIGHT: + return "right"; + case CHROMA_ANCHOR_TOP_LEFT: + return "top-left"; + case CHROMA_ANCHOR_TOP_RIGHT: + return "top-right"; + case CHROMA_ANCHOR_BOTTOM_LEFT: + return "bottom-left"; + case CHROMA_ANCHOR_BOTTOM_RIGHT: + return "bottom-right"; + default: + return "unknown"; + } +} + // Output-to-image mapping static int add_output_mapping(chroma_config_t *config, const char *output_name, const char *image_path, chroma_scale_mode_t scale_mode, - chroma_filter_quality_t filter_quality) { + chroma_filter_quality_t filter_quality, + chroma_anchor_t anchor) { if (!config || !output_name || !image_path) { return CHROMA_ERROR_INIT; } @@ -159,12 +217,14 @@ static int add_output_mapping(chroma_config_t *config, const char *output_name, strcpy(mapping->image_path, image_path); mapping->scale_mode = scale_mode; mapping->filter_quality = filter_quality; + mapping->anchor = anchor; config->mapping_count++; - chroma_log("DEBUG", "Added mapping: %s -> %s (scale: %s, filter: %s)", - output_name, image_path, scale_mode_to_string(scale_mode), - filter_quality_to_string(filter_quality)); + chroma_log( + "DEBUG", "Added mapping: %s -> %s (scale: %s, filter: %s, anchor: %s)", + output_name, image_path, scale_mode_to_string(scale_mode), + filter_quality_to_string(filter_quality), anchor_to_string(anchor)); chroma_log("TRACE", "Output mapping %d: '%s' -> '%s' (path length: %zu)", config->mapping_count, output_name, image_path, path_len); return CHROMA_OK; @@ -183,6 +243,7 @@ static void init_default_config(chroma_config_t *config) { // Set default scaling and filtering config->default_scale_mode = CHROMA_SCALE_FILL; config->default_filter_quality = CHROMA_FILTER_LINEAR; + config->default_anchor = CHROMA_ANCHOR_CENTER; // Set default downsampling settings config->enable_downsampling = true; // enable by default, performance etc. @@ -286,6 +347,10 @@ static int parse_config_line(chroma_config_t *config, char *line, config->enable_downsampling = parse_bool(value); chroma_log("DEBUG", "Set downsampling: %s", config->enable_downsampling ? "enabled" : "disabled"); + } else if (strcasecmp(key, "anchor") == 0) { + config->default_anchor = parse_anchor(value); + chroma_log("DEBUG", "Set default anchor: %s", + anchor_to_string(config->default_anchor)); } else if (strcasecmp(key, "max_output_width") == 0) { int width = atoi(value); if (width > 0 && width <= 16384) { // Reasonable limits @@ -351,6 +416,10 @@ static int parse_config_line(chroma_config_t *config, char *line, mapping->filter_quality = parse_filter_quality(value); chroma_log("DEBUG", "Set filter quality for output %s: %s", output_name, filter_quality_to_string(mapping->filter_quality)); + } else if (strcasecmp(property, "anchor") == 0) { + mapping->anchor = parse_anchor(value); + chroma_log("DEBUG", "Set anchor for output %s: %s", output_name, + anchor_to_string(mapping->anchor)); } else { chroma_log("WARN", "Unknown output property: %s (line %d)", property, line_number); @@ -375,7 +444,8 @@ static int parse_config_line(chroma_config_t *config, char *line, if (add_output_mapping(config, output_name, path_to_validate, config->default_scale_mode, - config->default_filter_quality) != CHROMA_OK) { + config->default_filter_quality, + config->default_anchor) != CHROMA_OK) { chroma_log("ERROR", "Failed to add output mapping: %s -> %s", output_name, path_to_validate); if (expanded_path) { @@ -518,12 +588,13 @@ const char *chroma_config_get_image_for_output(chroma_config_t *config, return NULL; } -// Get configuration mapping for output, including scale mode and filter -// quality +// Get configuration mapping for output, including scale mode, filter +// quality, and anchor int chroma_config_get_mapping_for_output( chroma_config_t *config, const char *output_name, - chroma_scale_mode_t *scale_mode, chroma_filter_quality_t *filter_quality) { - if (!config || !output_name || !scale_mode || !filter_quality) { + chroma_scale_mode_t *scale_mode, chroma_filter_quality_t *filter_quality, + chroma_anchor_t *anchor) { + if (!config || !output_name || !scale_mode || !filter_quality || !anchor) { return CHROMA_ERROR_INIT; } @@ -532,10 +603,13 @@ int chroma_config_get_mapping_for_output( if (strcmp(config->mappings[i].output_name, output_name) == 0) { *scale_mode = config->mappings[i].scale_mode; *filter_quality = config->mappings[i].filter_quality; + *anchor = config->mappings[i].anchor; chroma_log("DEBUG", - "Found specific mapping for output %s: scale=%s, filter=%s", + "Found specific mapping for output %s: scale=%s, filter=%s, " + "anchor=%s", output_name, scale_mode_to_string(*scale_mode), - filter_quality_to_string(*filter_quality)); + filter_quality_to_string(*filter_quality), + anchor_to_string(*anchor)); return CHROMA_OK; } } @@ -543,14 +617,14 @@ int chroma_config_get_mapping_for_output( // Return defaults if no specific mapping found *scale_mode = config->default_scale_mode; *filter_quality = config->default_filter_quality; - chroma_log("DEBUG", "Using defaults for output %s: scale=%s, filter=%s", - output_name, scale_mode_to_string(*scale_mode), - filter_quality_to_string(*filter_quality)); + *anchor = config->default_anchor; + chroma_log( + "DEBUG", "Using defaults for output %s: scale=%s, filter=%s, anchor=%s", + output_name, scale_mode_to_string(*scale_mode), + filter_quality_to_string(*filter_quality), anchor_to_string(*anchor)); return CHROMA_OK; } - - // Print current configuration for debugging void chroma_config_print(const chroma_config_t *config) { if (!config) { diff --git a/src/core.c b/src/core.c index d84887f..a8890c1 100644 --- a/src/core.c +++ b/src/core.c @@ -112,19 +112,21 @@ static int assign_wallpaper_to_output(chroma_state_t *state, // Store old configuration values for comparison chroma_scale_mode_t old_scale_mode = output->scale_mode; chroma_filter_quality_t old_filter_quality = output->filter_quality; + chroma_anchor_t old_anchor = output->anchor; bool had_config = output->config_loaded; - // Load configuration for this output (scale mode and filter quality) + // Load configuration for this output (scale mode, filter quality, anchor) if (chroma_config_get_mapping_for_output( &state->config, output->name ? output->name : "unknown", - &output->scale_mode, &output->filter_quality) == CHROMA_OK) { + &output->scale_mode, &output->filter_quality, &output->anchor) == CHROMA_OK) { output->config_loaded = true; - chroma_log("DEBUG", "Loaded config for output %u: scale=%d, filter=%d", - output->id, output->scale_mode, output->filter_quality); + chroma_log("DEBUG", "Loaded config for output %u: scale=%d, filter=%d, anchor=%d", + output->id, output->scale_mode, output->filter_quality, output->anchor); // Check if configuration changed and invalidate texture if needed if (had_config && (old_scale_mode != output->scale_mode || - old_filter_quality != output->filter_quality)) { + old_filter_quality != output->filter_quality || + old_anchor != output->anchor)) { chroma_output_invalidate_texture(output); output->vbo_dirty = true; // VBO needs update for new scale mode chroma_log("DEBUG", diff --git a/src/render.c b/src/render.c index f4c4a3f..281489f 100644 --- a/src/render.c +++ b/src/render.c @@ -41,8 +41,9 @@ static void get_gl_filter_params(chroma_filter_quality_t quality, } } -// Calculate texture coordinates based on scaling mode +// Calculate texture coordinates based on scaling mode and anchor position static void calculate_texture_coords(chroma_scale_mode_t scale_mode, + chroma_anchor_t anchor, int image_width, int image_height, int output_width, int output_height, float tex_coords[8]) { @@ -142,6 +143,72 @@ static void calculate_texture_coords(chroma_scale_mode_t scale_mode, break; } + // Apply anchor-based offset by shifting the visible crop region + // For fill: shift which part of image is shown + // For fit/center: shift where the image is positioned + // + // Positive shift reveals left/bottom portion + // Negative shift reveals right/top portion + float u_shift = 0.0f; + float v_shift = 0.0f; + + switch (anchor) { + case CHROMA_ANCHOR_CENTER: + u_shift = 0.0f; + v_shift = 0.0f; + break; + case CHROMA_ANCHOR_TOP: + u_shift = 0.0f; + v_shift = -0.5f; // shift up to show top of image + break; + case CHROMA_ANCHOR_BOTTOM: + u_shift = 0.0f; + v_shift = 0.5f; // shift down to show bottom of image + break; + case CHROMA_ANCHOR_LEFT: + u_shift = -0.5f; // shift left to show left of image + v_shift = 0.0f; + break; + case CHROMA_ANCHOR_RIGHT: + u_shift = 0.5f; // shift right to show right of image + v_shift = 0.0f; + break; + case CHROMA_ANCHOR_TOP_LEFT: + u_shift = -0.5f; + v_shift = -0.5f; + break; + case CHROMA_ANCHOR_TOP_RIGHT: + u_shift = 0.5f; + v_shift = -0.5f; + break; + case CHROMA_ANCHOR_BOTTOM_LEFT: + u_shift = -0.5f; + v_shift = 0.5f; + break; + case CHROMA_ANCHOR_BOTTOM_RIGHT: + u_shift = 0.5f; + v_shift = 0.5f; + break; + } + + // Calculate the shift amount based on crop/border space available + float u_crop = 1.0f - (u2 - u1); + float v_crop = 1.0f - (v2 - v1); + u1 += u_shift * u_crop; + u2 += u_shift * u_crop; + v1 += v_shift * v_crop; + v2 += v_shift * v_crop; + + // Clamp to valid range [0, 1] + if (u1 < 0.0f) + u1 = 0.0f; + if (u2 > 1.0f) + u2 = 1.0f; + if (v1 < 0.0f) + v1 = 0.0f; + if (v2 > 1.0f) + v2 = 1.0f; + // Set texture coordinates for quad (bottom-left, bottom-right, top-right, // top-left) tex_coords[0] = u1; @@ -688,11 +755,11 @@ int chroma_render_wallpaper(chroma_state_t *state, chroma_output_t *output) { // Update VBO only if needed. E.g, image changed, scale mode changed, or first // render if (output->vbo_dirty) { - // Calculate texture coordinates based on scaling mode + // Calculate texture coordinates based on scaling mode and anchor float tex_coords[8]; - calculate_texture_coords(output->scale_mode, output->image->width, - output->image->height, output->width, - output->height, tex_coords); + calculate_texture_coords(output->scale_mode, output->anchor, + output->image->width, output->image->height, + output->width, output->height, tex_coords); // Create dynamic vertex data with calculated texture coordinates float dynamic_vertices[] = {