brewery
notashelf /
ca468ce677b3f6c584b3483120f79f8ed58c9e96

chroma

public

Lightweight wallpaper daemon for Wayland

Star0tarball
clone
ssh://git@git.notashelf.dev:33/notashelf/chroma.git
src/image.cC248 lines6.6 KB
1
#define STB_IMAGE_IMPLEMENTATION
2
#include "../include/stb_image.h"
3
4
#include <stdio.h>
5
#include <stdlib.h>
6
#include <string.h>
7
#include <sys/stat.h>
8
9
#include "../include/chroma.h"
10
11
// Check if file exists and is readable
12
static int file_exists(const char *path) {
13
  struct stat st;
14
  return (stat(path, &st) == 0 && S_ISREG(st.st_mode));
15
}
16
17
// Get file size
18
static long get_file_size(const char *path) {
19
  struct stat st;
20
  if (stat(path, &st) != 0) {
21
    return -1;
22
  }
23
  return st.st_size;
24
}
25
26
// Load image from file
27
int chroma_image_load(chroma_image_t *image, const char *path) {
28
  if (!image || !path) {
29
    chroma_log("ERROR", "Invalid parameters for image loading");
30
    return CHROMA_ERROR_INIT;
31
  }
32
33
  // Initialize image structure
34
  memset(image, 0, sizeof(chroma_image_t));
35
  strncpy(image->path, path, MAX_PATH_LEN - 1);
36
  image->path[MAX_PATH_LEN - 1] = '\0';
37
38
  // Check if file exists
39
  if (!file_exists(path)) {
40
    chroma_log("ERROR", "Image file does not exist: %s", path);
41
    return CHROMA_ERROR_IMAGE;
42
  }
43
44
  // Get file size for logging
45
  long file_size = get_file_size(path);
46
  if (file_size > 0) {
47
    chroma_log("DEBUG", "Loading image: %s (%.2f MB)", path,
48
               (double)file_size / (1024.0 * 1024.0));
49
  }
50
51
  // Load image data using stb_image, force RGBA format to avoid conversion
52
  stbi_set_flip_vertically_on_load(0); // keep images right-side up
53
54
  image->data =
55
      stbi_load(path, &image->width, &image->height, &image->channels, 4);
56
  image->channels = 4; // always RGBA after forced conversion
57
  if (!image->data) {
58
    chroma_log("ERROR", "Failed to load image %s: %s", path,
59
               stbi_failure_reason());
60
    return CHROMA_ERROR_IMAGE;
61
  }
62
63
  // Validate image dimensions
64
  if (image->width <= 0 || image->height <= 0) {
65
    chroma_log("ERROR", "Invalid image dimensions: %dx%d", image->width,
66
               image->height);
67
    chroma_image_free(image);
68
    return CHROMA_ERROR_IMAGE;
69
  }
70
71
  // Validate we have RGBA data (should always be true with forced conversion)
72
  if (image->channels != 4) {
73
    chroma_log("ERROR", "Failed to load image as RGBA: got %d channels",
74
               image->channels);
75
    chroma_image_free(image);
76
    return CHROMA_ERROR_IMAGE;
77
  }
78
79
  image->loaded = true;
80
81
  // Calculate and log memory allocation
82
  size_t image_size = (size_t)image->width * image->height * image->channels;
83
  chroma_log_resource_allocation("image_data", image_size, path);
84
85
  chroma_log("INFO", "Loaded image: %s (%dx%d, %d channels, %.2f MB)", path,
86
             image->width, image->height, image->channels,
87
             (double)image_size / (1024.0 * 1024.0));
88
89
  return CHROMA_OK;
90
}
91
92
// Free image data
93
void chroma_image_free(chroma_image_t *image) {
94
  if (!image) {
95
    return;
96
  }
97
98
  if (image->data) {
99
    // Log memory deallocation before freeing
100
    size_t image_size = (size_t)image->width * image->height * image->channels;
101
    chroma_log_resource_deallocation("image_data", image_size, image->path);
102
103
    // Always use stbi_image_free since we load directly with stbi_load
104
    stbi_image_free(image->data);
105
    image->data = NULL;
106
  }
107
108
  image->width = 0;
109
  image->height = 0;
110
  image->channels = 0;
111
  image->loaded = false;
112
113
  if (strlen(image->path) > 0) {
114
    chroma_log("DEBUG", "Freed image: %s", image->path);
115
  }
116
117
  memset(image->path, 0, sizeof(image->path));
118
}
119
120
// Find image by path in state
121
chroma_image_t *chroma_image_find_by_path(chroma_state_t *state,
122
                                          const char *path) {
123
  if (!state || !path) {
124
    return NULL;
125
  }
126
127
  for (int i = 0; i < state->image_count; i++) {
128
    if (strcmp(state->images[i].path, path) == 0) {
129
      return &state->images[i];
130
    }
131
  }
132
133
  return NULL;
134
}
135
136
// Load image if not already loaded
137
chroma_image_t *chroma_image_get_or_load(chroma_state_t *state,
138
                                         const char *path) {
139
  if (!state || !path) {
140
    return NULL;
141
  }
142
143
  // Check if already loaded
144
  chroma_image_t *existing = chroma_image_find_by_path(state, path);
145
  if (existing && existing->loaded) {
146
    chroma_log("DEBUG", "Using cached image: %s", path);
147
    return existing;
148
  }
149
150
  // Find empty slot or reuse existing
151
  chroma_image_t *image = existing;
152
  if (!image) {
153
    if (state->image_count >= MAX_OUTPUTS) {
154
      chroma_log("ERROR", "Maximum number of images reached");
155
      return NULL;
156
    }
157
    image = &state->images[state->image_count];
158
    state->image_count++;
159
  }
160
161
  // Load the image
162
  if (chroma_image_load(image, path) != CHROMA_OK) {
163
    // If this was a new slot, decrement count
164
    if (!existing) {
165
      state->image_count--;
166
    }
167
    return NULL;
168
  }
169
170
  return image;
171
}
172
173
// Validate image file format
174
int chroma_image_validate(const char *path) {
175
  if (!path || !file_exists(path)) {
176
    return CHROMA_ERROR_IMAGE;
177
  }
178
179
  // Check file extension (basic validation)
180
  const char *ext = strrchr(path, '.');
181
  if (!ext) {
182
    return CHROMA_ERROR_IMAGE;
183
  }
184
185
  ext++; // Skip the dot
186
187
  // Check supported extensions
188
  if (strcasecmp(ext, "jpg") == 0 || strcasecmp(ext, "jpeg") == 0 ||
189
      strcasecmp(ext, "png") == 0 || strcasecmp(ext, "bmp") == 0 ||
190
      strcasecmp(ext, "tga") == 0 || strcasecmp(ext, "psd") == 0 ||
191
      strcasecmp(ext, "gif") == 0 || strcasecmp(ext, "hdr") == 0 ||
192
      strcasecmp(ext, "pic") == 0 || strcasecmp(ext, "ppm") == 0 ||
193
      strcasecmp(ext, "pgm") == 0) {
194
    return CHROMA_OK;
195
  }
196
197
  chroma_log("WARN", "Potentially unsupported image format: %s", ext);
198
  return CHROMA_ERROR_IMAGE;
199
}
200
201
// Get image info without loading full data
202
int chroma_image_get_info(const char *path, int *width, int *height,
203
                          int *channels) {
204
  if (!path || !width || !height || !channels) {
205
    return CHROMA_ERROR_INIT;
206
  }
207
208
  if (!file_exists(path)) {
209
    return CHROMA_ERROR_IMAGE;
210
  }
211
212
  if (!stbi_info(path, width, height, channels)) {
213
    chroma_log("ERROR", "Failed to get image info for %s: %s", path,
214
               stbi_failure_reason());
215
    return CHROMA_ERROR_IMAGE;
216
  }
217
218
  return CHROMA_OK;
219
}
220
221
// Cleanup all images in state
222
void chroma_images_cleanup(chroma_state_t *state) {
223
  if (!state) {
224
    return;
225
  }
226
227
  chroma_log("DEBUG", "Cleaning up %d images", state->image_count);
228
229
  for (int i = 0; i < state->image_count; i++) {
230
    chroma_image_free(&state->images[i]);
231
  }
232
233
  state->image_count = 0;
234
  chroma_log("INFO", "Cleaned up all images");
235
  chroma_log_memory_stats("post-image-cleanup");
236
}
237
238
// Preload common image formats for validation
239
void chroma_image_init_stb(void) {
240
  // Set stb_image options
241
  stbi_set_flip_vertically_on_load(0);
242
243
  // These could be made configurable
244
  stbi_ldr_to_hdr_gamma(2.2f);
245
  stbi_ldr_to_hdr_scale(1.0f);
246
247
  chroma_log("DEBUG", "Initialized stb_image library");
248
}