brewery
notashelf /
74fed80a26f39bb04966d12c649a55150724965f

chroma

public

Lightweight wallpaper daemon for Wayland

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