{render,config}: allow specifying wallpaper anchor position

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: Iee73e9d149e85d2c00eaba4be25d42bd6a6a6964
This commit is contained in:
raf 2026-04-13 14:25:47 +03:00
commit a82b986ac6
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
4 changed files with 189 additions and 29 deletions

19
include/chroma.h vendored
View file

@ -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);

View file

@ -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) {

View file

@ -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",

View file

@ -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[] = {