brewery
notashelf /
87d445340a666b2f700aedcc26fabe8f771b7272

chroma

public

Lightweight wallpaper daemon for Wayland

Star0tarball
clone
ssh://git@git.notashelf.dev:33/notashelf/chroma.git
src/image.cC268 lines7.0 KB
1
#define STB_IMAGE_IMPLEMENTATION
2
#include "../include/stb_image.h"
3
4
#include <errno.h>
5
#include <stdio.h>
6
#include <stdlib.h>
7
#include <string.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
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, 0);
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
  // Check supported formats
72
  if (image->channels < 3 || image->channels > 4) {
73
    chroma_log("ERROR",
74
               "Unsupported image format: %d channels (need RGB or RGBA)",
75
               image->channels);
76
    chroma_image_free(image);
77
    return CHROMA_ERROR_IMAGE;
78
  }
79
80
  // Convert RGB to RGBA if necessary for consistent handling
81
  if (image->channels == 3) {
82
    int pixel_count = image->width * image->height;
83
    unsigned char *rgba_data = malloc(pixel_count * 4);
84
    if (!rgba_data) {
85
      chroma_log("ERROR", "Failed to allocate memory for RGBA conversion");
86
      chroma_image_free(image);
87
      return CHROMA_ERROR_MEMORY;
88
    }
89
90
    // Convert RGB to RGBA
91
    for (int i = 0; i < pixel_count; i++) {
92
      rgba_data[i * 4 + 0] = image->data[i * 3 + 0]; // R
93
      rgba_data[i * 4 + 1] = image->data[i * 3 + 1]; // G
94
      rgba_data[i * 4 + 2] = image->data[i * 3 + 2]; // B
95
      rgba_data[i * 4 + 3] = 255;                    // A
96
    }
97
98
    // Replace original data
99
    stbi_image_free(image->data);
100
    image->data = rgba_data;
101
    image->channels = 4;
102
  }
103
104
  image->loaded = true;
105
106
  chroma_log("INFO", "Loaded image: %s (%dx%d, %d channels, %.2f MB)", path,
107
             image->width, image->height, image->channels,
108
             (double)(image->width * image->height * image->channels) /
109
                 (1024.0 * 1024.0));
110
111
  return CHROMA_OK;
112
}
113
114
// Free image data
115
void chroma_image_free(chroma_image_t *image) {
116
  if (!image) {
117
    return;
118
  }
119
120
  if (image->data) {
121
    if (image->channels == 4 && strlen(image->path) > 0) {
122
      // If we converted from RGB to RGBA, use regular free()
123
      free(image->data);
124
    } else {
125
      // If loaded directly by stb_image, use stbi_image_free()
126
      stbi_image_free(image->data);
127
    }
128
    image->data = NULL;
129
  }
130
131
  image->width = 0;
132
  image->height = 0;
133
  image->channels = 0;
134
  image->loaded = false;
135
136
  if (strlen(image->path) > 0) {
137
    chroma_log("DEBUG", "Freed image: %s", image->path);
138
  }
139
140
  memset(image->path, 0, sizeof(image->path));
141
}
142
143
// Find image by path in state
144
chroma_image_t *chroma_image_find_by_path(chroma_state_t *state,
145
                                          const char *path) {
146
  if (!state || !path) {
147
    return NULL;
148
  }
149
150
  for (int i = 0; i < state->image_count; i++) {
151
    if (strcmp(state->images[i].path, path) == 0) {
152
      return &state->images[i];
153
    }
154
  }
155
156
  return NULL;
157
}
158
159
// Load image if not already loaded
160
chroma_image_t *chroma_image_get_or_load(chroma_state_t *state,
161
                                         const char *path) {
162
  if (!state || !path) {
163
    return NULL;
164
  }
165
166
  // Check if already loaded
167
  chroma_image_t *existing = chroma_image_find_by_path(state, path);
168
  if (existing && existing->loaded) {
169
    chroma_log("DEBUG", "Using cached image: %s", path);
170
    return existing;
171
  }
172
173
  // Find empty slot or reuse existing
174
  chroma_image_t *image = existing;
175
  if (!image) {
176
    if (state->image_count >= MAX_OUTPUTS) {
177
      chroma_log("ERROR", "Maximum number of images reached");
178
      return NULL;
179
    }
180
    image = &state->images[state->image_count];
181
    state->image_count++;
182
  }
183
184
  // Load the image
185
  if (chroma_image_load(image, path) != CHROMA_OK) {
186
    // If this was a new slot, decrement count
187
    if (!existing) {
188
      state->image_count--;
189
    }
190
    return NULL;
191
  }
192
193
  return image;
194
}
195
196
// Validate image file format
197
int chroma_image_validate(const char *path) {
198
  if (!path || !file_exists(path)) {
199
    return CHROMA_ERROR_IMAGE;
200
  }
201
202
  // Check file extension (basic validation)
203
  const char *ext = strrchr(path, '.');
204
  if (!ext) {
205
    return CHROMA_ERROR_IMAGE;
206
  }
207
208
  ext++; // Skip the dot
209
210
  // Check supported extensions
211
  if (strcasecmp(ext, "jpg") == 0 || strcasecmp(ext, "jpeg") == 0 ||
212
      strcasecmp(ext, "png") == 0 || strcasecmp(ext, "bmp") == 0 ||
213
      strcasecmp(ext, "tga") == 0 || strcasecmp(ext, "psd") == 0 ||
214
      strcasecmp(ext, "gif") == 0 || strcasecmp(ext, "hdr") == 0 ||
215
      strcasecmp(ext, "pic") == 0 || strcasecmp(ext, "ppm") == 0 ||
216
      strcasecmp(ext, "pgm") == 0) {
217
    return CHROMA_OK;
218
  }
219
220
  chroma_log("WARN", "Potentially unsupported image format: %s", ext);
221
  return CHROMA_ERROR_IMAGE;
222
}
223
224
// Get image info without loading full data
225
int chroma_image_get_info(const char *path, int *width, int *height,
226
                          int *channels) {
227
  if (!path || !width || !height || !channels) {
228
    return CHROMA_ERROR_INIT;
229
  }
230
231
  if (!file_exists(path)) {
232
    return CHROMA_ERROR_IMAGE;
233
  }
234
235
  if (!stbi_info(path, width, height, channels)) {
236
    chroma_log("ERROR", "Failed to get image info for %s: %s", path,
237
               stbi_failure_reason());
238
    return CHROMA_ERROR_IMAGE;
239
  }
240
241
  return CHROMA_OK;
242
}
243
244
// Cleanup all images in state
245
void chroma_images_cleanup(chroma_state_t *state) {
246
  if (!state) {
247
    return;
248
  }
249
250
  for (int i = 0; i < state->image_count; i++) {
251
    chroma_image_free(&state->images[i]);
252
  }
253
254
  state->image_count = 0;
255
  chroma_log("INFO", "Cleaned up all images");
256
}
257
258
// Preload common image formats for validation
259
void chroma_image_init_stb(void) {
260
  // Set stb_image options
261
  stbi_set_flip_vertically_on_load(0);
262
263
  // These could be made configurable
264
  stbi_ldr_to_hdr_gamma(2.2f);
265
  stbi_ldr_to_hdr_scale(1.0f);
266
267
  chroma_log("DEBUG", "Initialized stb_image library");
268
}