brewery
notashelf /
e72da82b32d46daf27c4ff663e5f918058b65fd7

chroma

public

Lightweight wallpaper daemon for Wayland

Star0tarball
clone
ssh://git@git.notashelf.dev:33/notashelf/chroma.git
src/image.cC239 lines6.2 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, 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
  chroma_log("INFO", "Loaded image: %s (%dx%d, %d channels, %.2f MB)", path,
83
             image->width, image->height, image->channels,
84
             (double)(image->width * image->height * image->channels) /
85
                 (1024.0 * 1024.0));
86
87
  return CHROMA_OK;
88
}
89
90
// Free image data
91
void chroma_image_free(chroma_image_t *image) {
92
  if (!image) {
93
    return;
94
  }
95
96
  if (image->data) {
97
    // Always use stbi_image_free since we load directly with stbi_load
98
    stbi_image_free(image->data);
99
    image->data = NULL;
100
  }
101
102
  image->width = 0;
103
  image->height = 0;
104
  image->channels = 0;
105
  image->loaded = false;
106
107
  if (strlen(image->path) > 0) {
108
    chroma_log("DEBUG", "Freed image: %s", image->path);
109
  }
110
111
  memset(image->path, 0, sizeof(image->path));
112
}
113
114
// Find image by path in state
115
chroma_image_t *chroma_image_find_by_path(chroma_state_t *state,
116
                                          const char *path) {
117
  if (!state || !path) {
118
    return NULL;
119
  }
120
121
  for (int i = 0; i < state->image_count; i++) {
122
    if (strcmp(state->images[i].path, path) == 0) {
123
      return &state->images[i];
124
    }
125
  }
126
127
  return NULL;
128
}
129
130
// Load image if not already loaded
131
chroma_image_t *chroma_image_get_or_load(chroma_state_t *state,
132
                                         const char *path) {
133
  if (!state || !path) {
134
    return NULL;
135
  }
136
137
  // Check if already loaded
138
  chroma_image_t *existing = chroma_image_find_by_path(state, path);
139
  if (existing && existing->loaded) {
140
    chroma_log("DEBUG", "Using cached image: %s", path);
141
    return existing;
142
  }
143
144
  // Find empty slot or reuse existing
145
  chroma_image_t *image = existing;
146
  if (!image) {
147
    if (state->image_count >= MAX_OUTPUTS) {
148
      chroma_log("ERROR", "Maximum number of images reached");
149
      return NULL;
150
    }
151
    image = &state->images[state->image_count];
152
    state->image_count++;
153
  }
154
155
  // Load the image
156
  if (chroma_image_load(image, path) != CHROMA_OK) {
157
    // If this was a new slot, decrement count
158
    if (!existing) {
159
      state->image_count--;
160
    }
161
    return NULL;
162
  }
163
164
  return image;
165
}
166
167
// Validate image file format
168
int chroma_image_validate(const char *path) {
169
  if (!path || !file_exists(path)) {
170
    return CHROMA_ERROR_IMAGE;
171
  }
172
173
  // Check file extension (basic validation)
174
  const char *ext = strrchr(path, '.');
175
  if (!ext) {
176
    return CHROMA_ERROR_IMAGE;
177
  }
178
179
  ext++; // Skip the dot
180
181
  // Check supported extensions
182
  if (strcasecmp(ext, "jpg") == 0 || strcasecmp(ext, "jpeg") == 0 ||
183
      strcasecmp(ext, "png") == 0 || strcasecmp(ext, "bmp") == 0 ||
184
      strcasecmp(ext, "tga") == 0 || strcasecmp(ext, "psd") == 0 ||
185
      strcasecmp(ext, "gif") == 0 || strcasecmp(ext, "hdr") == 0 ||
186
      strcasecmp(ext, "pic") == 0 || strcasecmp(ext, "ppm") == 0 ||
187
      strcasecmp(ext, "pgm") == 0) {
188
    return CHROMA_OK;
189
  }
190
191
  chroma_log("WARN", "Potentially unsupported image format: %s", ext);
192
  return CHROMA_ERROR_IMAGE;
193
}
194
195
// Get image info without loading full data
196
int chroma_image_get_info(const char *path, int *width, int *height,
197
                          int *channels) {
198
  if (!path || !width || !height || !channels) {
199
    return CHROMA_ERROR_INIT;
200
  }
201
202
  if (!file_exists(path)) {
203
    return CHROMA_ERROR_IMAGE;
204
  }
205
206
  if (!stbi_info(path, width, height, channels)) {
207
    chroma_log("ERROR", "Failed to get image info for %s: %s", path,
208
               stbi_failure_reason());
209
    return CHROMA_ERROR_IMAGE;
210
  }
211
212
  return CHROMA_OK;
213
}
214
215
// Cleanup all images in state
216
void chroma_images_cleanup(chroma_state_t *state) {
217
  if (!state) {
218
    return;
219
  }
220
221
  for (int i = 0; i < state->image_count; i++) {
222
    chroma_image_free(&state->images[i]);
223
  }
224
225
  state->image_count = 0;
226
  chroma_log("INFO", "Cleaned up all images");
227
}
228
229
// Preload common image formats for validation
230
void chroma_image_init_stb(void) {
231
  // Set stb_image options
232
  stbi_set_flip_vertically_on_load(0);
233
234
  // These could be made configurable
235
  stbi_ldr_to_hdr_gamma(2.2f);
236
  stbi_ldr_to_hdr_scale(1.0f);
237
238
  chroma_log("DEBUG", "Initialized stb_image library");
239
}