brewery
notashelf /
0732ddd427d053e70468db8147f14e4a2eba127d

chroma

public

Lightweight wallpaper daemon for Wayland

Star0tarball
clone
ssh://git@git.notashelf.dev:33/notashelf/chroma.git
src/image_loaders.cC620 lines15.9 KB
1
// Image format loaders for formats not supported by stb_image.
2
// Each loader converts its format to RGBA8 and fills a chroma_image_t.
3
// Loaders requiring optional libraries are guarded by #ifdef CHROMA_HAS_*;
4
// when absent the function returns CHROMA_ERROR_IMAGE with a build hint.
5
6
#include <stdio.h>
7
#include <stdlib.h>
8
#include <string.h>
9
10
#include "../include/chroma.h"
11
12
static uint32_t read_be32(const unsigned char *buf) {
13
  return ((uint32_t)buf[0] << 24) | ((uint32_t)buf[1] << 16) |
14
         ((uint32_t)buf[2] << 8) | (uint32_t)buf[3];
15
}
16
17
static uint16_t read_be16(const unsigned char *buf) {
18
  return (uint16_t)(((uint16_t)buf[0] << 8) | (uint16_t)buf[1]);
19
}
20
21
// Farbfeld is a lossless image format: 16-byte magic + RGBA16 big-endian.
22
// Spec: https://tools.suckless.org/farbfeld/
23
int chroma_image_load_farbfeld(chroma_image_t *image, const char *path) {
24
  if (!image || !path) {
25
    return CHROMA_ERROR_INIT;
26
  }
27
28
  FILE *fp = fopen(path, "rb");
29
  if (!fp) {
30
    chroma_log("ERROR", "Cannot open farbfeld file: %s", path);
31
    return CHROMA_ERROR_IMAGE;
32
  }
33
34
  unsigned char header[16];
35
  if (fread(header, 1, 16, fp) != 16) {
36
    chroma_log("ERROR", "Failed to read farbfeld header: %s", path);
37
    fclose(fp);
38
    return CHROMA_ERROR_IMAGE;
39
  }
40
41
  if (memcmp(header, "farbfeld", 8) != 0) {
42
    chroma_log("ERROR", "Invalid farbfeld signature in: %s", path);
43
    fclose(fp);
44
    return CHROMA_ERROR_IMAGE;
45
  }
46
47
  int width = (int)read_be32(header + 8);
48
  int height = (int)read_be32(header + 12);
49
50
  if (width <= 0 || height <= 0 || width > 16384 || height > 16384) {
51
    chroma_log("ERROR", "Invalid farbfeld dimensions: %dx%d", width, height);
52
    fclose(fp);
53
    return CHROMA_ERROR_IMAGE;
54
  }
55
56
  size_t pixel_count = (size_t)width * (size_t)height;
57
  size_t src_size = pixel_count * 8;
58
  size_t dst_size = pixel_count * 4;
59
60
  unsigned char *src_data = malloc(src_size);
61
  if (!src_data) {
62
    fclose(fp);
63
    return CHROMA_ERROR_MEMORY;
64
  }
65
66
  if (fread(src_data, 1, src_size, fp) != src_size) {
67
    chroma_log("ERROR", "Truncated farbfeld data: %s", path);
68
    free(src_data);
69
    fclose(fp);
70
    return CHROMA_ERROR_IMAGE;
71
  }
72
  fclose(fp);
73
74
  unsigned char *dst_data = malloc(dst_size);
75
  if (!dst_data) {
76
    free(src_data);
77
    return CHROMA_ERROR_MEMORY;
78
  }
79
80
  for (size_t i = 0; i < pixel_count; i++) {
81
    size_t s = i * 8;
82
    size_t d = i * 4;
83
    dst_data[d + 0] = (unsigned char)(read_be16(src_data + s + 0) >> 8);
84
    dst_data[d + 1] = (unsigned char)(read_be16(src_data + s + 2) >> 8);
85
    dst_data[d + 2] = (unsigned char)(read_be16(src_data + s + 4) >> 8);
86
    dst_data[d + 3] = (unsigned char)(read_be16(src_data + s + 6) >> 8);
87
  }
88
89
  free(src_data);
90
91
  memset(image, 0, sizeof(chroma_image_t));
92
  strncpy(image->path, path, MAX_PATH_LEN - 1);
93
  image->data = dst_data;
94
  image->width = width;
95
  image->height = height;
96
  image->channels = 4;
97
  image->loaded = true;
98
  image->data_from_stbi = false;
99
  image->ref_count = 1;
100
101
  chroma_log("INFO", "Loaded farbfeld image: %s (%dx%d)", path, width, height);
102
  return CHROMA_OK;
103
}
104
105
// SVG rasterization via nanosvg (header-only). Enable with
106
// -DCHROMA_HAS_NANOSVG. Renders at output resolution if provided, otherwise
107
// uses the SVG viewport size.
108
#ifdef CHROMA_HAS_NANOSVG
109
110
#define NANOSVG_IMPLEMENTATION
111
#define NANOSVGRAST_IMPLEMENTATION
112
#include "../include/vendor/nanosvg.h"
113
#include "../include/vendor/nanosvgrast.h"
114
115
int chroma_image_load_svg(chroma_image_t *image, const char *path,
116
                          int out_width, int out_height) {
117
  if (!image || !path) {
118
    return CHROMA_ERROR_INIT;
119
  }
120
121
  FILE *fp = fopen(path, "rb");
122
  if (!fp) {
123
    chroma_log("ERROR", "Cannot open SVG file: %s", path);
124
    return CHROMA_ERROR_IMAGE;
125
  }
126
127
  fseek(fp, 0, SEEK_END);
128
  long size = ftell(fp);
129
  fseek(fp, 0, SEEK_SET);
130
131
  char *svg_data = malloc((size_t)size + 1);
132
  if (!svg_data) {
133
    fclose(fp);
134
    return CHROMA_ERROR_MEMORY;
135
  }
136
  if (fread(svg_data, 1, (size_t)size, fp) != (size_t)size) {
137
    chroma_log("ERROR", "Failed to read SVG file: %s", path);
138
    free(svg_data);
139
    fclose(fp);
140
    return CHROMA_ERROR_IMAGE;
141
  }
142
  svg_data[size] = '\0';
143
  fclose(fp);
144
145
  struct NSVGimage *nsvg = nsvgParse(svg_data, "px", 96);
146
  free(svg_data);
147
148
  if (!nsvg) {
149
    chroma_log("ERROR", "Failed to parse SVG: %s", path);
150
    return CHROMA_ERROR_IMAGE;
151
  }
152
153
  int w = out_width > 0 ? out_width : (int)nsvg->width;
154
  int h = out_height > 0 ? out_height : (int)nsvg->height;
155
  if (w <= 0)
156
    w = 800;
157
  if (h <= 0)
158
    h = 600;
159
160
  struct NSVGrasterizer *rast = nsvgCreateRasterizer();
161
  if (!rast) {
162
    nsvgDelete(nsvg);
163
    return CHROMA_ERROR_MEMORY;
164
  }
165
166
  unsigned char *data = malloc((size_t)w * (size_t)h * 4);
167
  if (!data) {
168
    nsvgDeleteRasterizer(rast);
169
    nsvgDelete(nsvg);
170
    return CHROMA_ERROR_MEMORY;
171
  }
172
173
  float scale_x = (float)w / nsvg->width;
174
  float scale_y = (float)h / nsvg->height;
175
  float scale = (scale_x < scale_y) ? scale_x : scale_y;
176
  nsvgRasterize(rast, nsvg, 0, 0, scale, data, w, h, w * 4);
177
178
  nsvgDeleteRasterizer(rast);
179
  nsvgDelete(nsvg);
180
181
  memset(image, 0, sizeof(chroma_image_t));
182
  strncpy(image->path, path, MAX_PATH_LEN - 1);
183
  image->data = data;
184
  image->width = w;
185
  image->height = h;
186
  image->channels = 4;
187
  image->loaded = true;
188
  image->data_from_stbi = false;
189
  image->ref_count = 1;
190
191
  chroma_log("INFO", "Loaded SVG image: %s (%dx%d)", path, w, h);
192
  return CHROMA_OK;
193
}
194
195
#else
196
197
int chroma_image_load_svg(chroma_image_t *image, const char *path,
198
                          int out_width, int out_height) {
199
  (void)image;
200
  (void)path;
201
  (void)out_width;
202
  (void)out_height;
203
  chroma_log("ERROR", "SVG support requires nanosvg. Rebuild with: "
204
                      "make CHROMA_FEATURES=svg");
205
  return CHROMA_ERROR_IMAGE;
206
}
207
208
#endif
209
210
// WebP decoding via libwebp. Enable with -DCHROMA_HAS_WEBP -lwebp.
211
#ifdef CHROMA_HAS_WEBP
212
213
#include <webp/decode.h>
214
215
int chroma_image_load_webp(chroma_image_t *image, const char *path) {
216
  if (!image || !path) {
217
    return CHROMA_ERROR_INIT;
218
  }
219
220
  FILE *fp = fopen(path, "rb");
221
  if (!fp) {
222
    chroma_log("ERROR", "Cannot open WebP file: %s", path);
223
    return CHROMA_ERROR_IMAGE;
224
  }
225
226
  fseek(fp, 0, SEEK_END);
227
  long size = ftell(fp);
228
  fseek(fp, 0, SEEK_SET);
229
230
  if (size <= 0 || size > 256 * 1024 * 1024) {
231
    chroma_log("ERROR", "WebP file too large: %s", path);
232
    fclose(fp);
233
    return CHROMA_ERROR_IMAGE;
234
  }
235
236
  unsigned char *webp_data = malloc((size_t)size);
237
  if (!webp_data) {
238
    fclose(fp);
239
    return CHROMA_ERROR_MEMORY;
240
  }
241
  if (fread(webp_data, 1, (size_t)size, fp) != (size_t)size) {
242
    chroma_log("ERROR", "Failed to read WebP file: %s", path);
243
    free(webp_data);
244
    fclose(fp);
245
    return CHROMA_ERROR_IMAGE;
246
  }
247
  fclose(fp);
248
249
  int w, h;
250
  if (!WebPGetInfo(webp_data, (size_t)size, &w, &h)) {
251
    chroma_log("ERROR", "Invalid WebP data: %s", path);
252
    free(webp_data);
253
    return CHROMA_ERROR_IMAGE;
254
  }
255
256
  unsigned char *data = WebPDecodeRGBA(webp_data, (size_t)size, &w, &h);
257
  free(webp_data);
258
259
  if (!data) {
260
    chroma_log("ERROR", "Failed to decode WebP: %s", path);
261
    return CHROMA_ERROR_IMAGE;
262
  }
263
264
  memset(image, 0, sizeof(chroma_image_t));
265
  strncpy(image->path, path, MAX_PATH_LEN - 1);
266
  image->data = data;
267
  image->width = w;
268
  image->height = h;
269
  image->channels = 4;
270
  image->loaded = true;
271
  image->data_from_stbi = false;
272
  image->ref_count = 1;
273
274
  chroma_log("INFO", "Loaded WebP image: %s (%dx%d)", path, w, h);
275
  return CHROMA_OK;
276
}
277
278
#else
279
280
int chroma_image_load_webp(chroma_image_t *image, const char *path) {
281
  (void)image;
282
  (void)path;
283
  chroma_log("ERROR", "WebP support requires libwebp. Rebuild with: "
284
                      "make CHROMA_FEATURES=webp");
285
  return CHROMA_ERROR_IMAGE;
286
}
287
288
#endif
289
290
// TIFF decoding via libtiff. Enable with -DCHROMA_HAS_TIFF -ltiff.
291
#ifdef CHROMA_HAS_TIFF
292
293
#include <tiffio.h>
294
295
int chroma_image_load_tiff(chroma_image_t *image, const char *path) {
296
  if (!image || !path) {
297
    return CHROMA_ERROR_INIT;
298
  }
299
300
  TIFF *tif = TIFFOpen(path, "r");
301
  if (!tif) {
302
    chroma_log("ERROR", "Cannot open TIFF file: %s", path);
303
    return CHROMA_ERROR_IMAGE;
304
  }
305
306
  uint32_t w, h;
307
  TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &w);
308
  TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &h);
309
310
  if (w == 0 || h == 0 || w > 32768 || h > 32768) {
311
    chroma_log("ERROR", "Invalid TIFF dimensions: %ux%u", w, h);
312
    TIFFClose(tif);
313
    return CHROMA_ERROR_IMAGE;
314
  }
315
316
  size_t pixel_count = (size_t)w * (size_t)h;
317
  size_t buf_size = pixel_count * 4;
318
  unsigned char *data = malloc(buf_size);
319
  if (!data) {
320
    TIFFClose(tif);
321
    return CHROMA_ERROR_MEMORY;
322
  }
323
324
  if (!TIFFReadRGBAImageOriented(tif, w, h, (uint32_t *)data,
325
                                 ORIENTATION_TOPLEFT, 0)) {
326
    chroma_log("ERROR", "Failed to decode TIFF: %s", path);
327
    free(data);
328
    TIFFClose(tif);
329
    return CHROMA_ERROR_IMAGE;
330
  }
331
332
  TIFFClose(tif);
333
334
  memset(image, 0, sizeof(chroma_image_t));
335
  strncpy(image->path, path, MAX_PATH_LEN - 1);
336
  image->data = data;
337
  image->width = (int)w;
338
  image->height = (int)h;
339
  image->channels = 4;
340
  image->loaded = true;
341
  image->data_from_stbi = false;
342
  image->ref_count = 1;
343
344
  chroma_log("INFO", "Loaded TIFF image: %s (%dx%d)", path, (int)w, (int)h);
345
  return CHROMA_OK;
346
}
347
348
#else
349
350
int chroma_image_load_tiff(chroma_image_t *image, const char *path) {
351
  (void)image;
352
  (void)path;
353
  chroma_log("ERROR", "TIFF support requires libtiff. Rebuild with: "
354
                      "make CHROMA_FEATURES=tiff");
355
  return CHROMA_ERROR_IMAGE;
356
}
357
358
#endif
359
360
// AVIF decoding via libavif. Enable with -DCHROMA_HAS_AVIF -lavif.
361
#ifdef CHROMA_HAS_AVIF
362
363
#include <avif/avif.h>
364
365
int chroma_image_load_avif(chroma_image_t *image, const char *path) {
366
  if (!image || !path) {
367
    return CHROMA_ERROR_INIT;
368
  }
369
370
  FILE *fp = fopen(path, "rb");
371
  if (!fp) {
372
    chroma_log("ERROR", "Cannot open AVIF file: %s", path);
373
    return CHROMA_ERROR_IMAGE;
374
  }
375
376
  fseek(fp, 0, SEEK_END);
377
  long size = ftell(fp);
378
  fseek(fp, 0, SEEK_SET);
379
380
  if (size <= 0 || size > 256 * 1024 * 1024) {
381
    fclose(fp);
382
    return CHROMA_ERROR_IMAGE;
383
  }
384
385
  avifRWData raw = AVIF_DATA_EMPTY;
386
  avifRWDataRealloc(&raw, (size_t)size);
387
  if (!raw.data) {
388
    fclose(fp);
389
    return CHROMA_ERROR_MEMORY;
390
  }
391
  if (fread(raw.data, 1, (size_t)size, fp) != (size_t)size) {
392
    chroma_log("ERROR", "Failed to read AVIF file: %s", path);
393
    avifRWDataFree(&raw);
394
    fclose(fp);
395
    return CHROMA_ERROR_IMAGE;
396
  }
397
  fclose(fp);
398
399
  avifDecoder *decoder = avifDecoderCreate();
400
  avifResult result = avifDecoderSetIOMemory(decoder, raw.data, raw.size);
401
  if (result != AVIF_RESULT_OK) {
402
    chroma_log("ERROR", "Failed to set AVIF IO: %s", path);
403
    avifDecoderDestroy(decoder);
404
    avifRWDataFree(&raw);
405
    return CHROMA_ERROR_IMAGE;
406
  }
407
408
  result = avifDecoderParse(decoder);
409
  if (result != AVIF_RESULT_OK) {
410
    chroma_log("ERROR", "Failed to parse AVIF: %s", path);
411
    avifDecoderDestroy(decoder);
412
    avifRWDataFree(&raw);
413
    return CHROMA_ERROR_IMAGE;
414
  }
415
416
  result = avifDecoderNextImage(decoder);
417
  if (result != AVIF_RESULT_OK) {
418
    chroma_log("ERROR", "Failed to decode AVIF: %s", path);
419
    avifDecoderDestroy(decoder);
420
    avifRWDataFree(&raw);
421
    return CHROMA_ERROR_IMAGE;
422
  }
423
424
  int w = (int)decoder->image->width;
425
  int h = (int)decoder->image->height;
426
427
  avifRGBImage rgb;
428
  avifRGBImageSetDefaults(&rgb, decoder->image);
429
  rgb.format = AVIF_RGB_FORMAT_RGBA;
430
  rgb.depth = 8;
431
432
  avifRGBImageAllocatePixels(&rgb);
433
  avifImageYUVToRGB(decoder->image, &rgb);
434
435
  unsigned char *data = malloc(rgb.rowBytes * (size_t)h);
436
  if (!data) {
437
    avifRGBImageFreePixels(&rgb);
438
    avifDecoderDestroy(decoder);
439
    avifRWDataFree(&raw);
440
    return CHROMA_ERROR_MEMORY;
441
  }
442
  memcpy(data, rgb.pixels, rgb.rowBytes * (size_t)h);
443
444
  avifRGBImageFreePixels(&rgb);
445
  avifDecoderDestroy(decoder);
446
  avifRWDataFree(&raw);
447
448
  memset(image, 0, sizeof(chroma_image_t));
449
  strncpy(image->path, path, MAX_PATH_LEN - 1);
450
  image->data = data;
451
  image->width = w;
452
  image->height = h;
453
  image->channels = 4;
454
  image->loaded = true;
455
  image->data_from_stbi = false;
456
  image->ref_count = 1;
457
458
  chroma_log("INFO", "Loaded AVIF image: %s (%dx%d)", path, w, h);
459
  return CHROMA_OK;
460
}
461
462
#else
463
464
int chroma_image_load_avif(chroma_image_t *image, const char *path) {
465
  (void)image;
466
  (void)path;
467
  chroma_log("ERROR", "AVIF support requires libavif. Rebuild with: "
468
                      "make CHROMA_FEATURES=avif");
469
  return CHROMA_ERROR_IMAGE;
470
}
471
472
#endif
473
474
// JPEG XL decoding via libjxl. Enable with -DCHROMA_HAS_JPEGXL -ljxl.
475
#ifdef CHROMA_HAS_JPEGXL
476
477
#include <jxl/decode.h>
478
#include <jxl/decode_cxx.h>
479
480
int chroma_image_load_jpegxl(chroma_image_t *image, const char *path) {
481
  if (!image || !path) {
482
    return CHROMA_ERROR_INIT;
483
  }
484
485
  FILE *fp = fopen(path, "rb");
486
  if (!fp) {
487
    chroma_log("ERROR", "Cannot open JPEG XL file: %s", path);
488
    return CHROMA_ERROR_IMAGE;
489
  }
490
491
  fseek(fp, 0, SEEK_END);
492
  long size = ftell(fp);
493
  fseek(fp, 0, SEEK_SET);
494
495
  if (size <= 0 || size > 256 * 1024 * 1024) {
496
    fclose(fp);
497
    return CHROMA_ERROR_IMAGE;
498
  }
499
500
  unsigned char *jxl_data = malloc((size_t)size);
501
  if (!jxl_data) {
502
    fclose(fp);
503
    return CHROMA_ERROR_MEMORY;
504
  }
505
  if (fread(jxl_data, 1, (size_t)size, fp) != (size_t)size) {
506
    chroma_log("ERROR", "Failed to read JPEG XL file: %s", path);
507
    free(jxl_data);
508
    fclose(fp);
509
    return CHROMA_ERROR_IMAGE;
510
  }
511
  fclose(fp);
512
513
  JxlDecoder *dec = JxlDecoderCreate(NULL);
514
  if (!dec) {
515
    free(jxl_data);
516
    return CHROMA_ERROR_MEMORY;
517
  }
518
519
  JxlDecoderStatus status = JxlDecoderSetInput(dec, jxl_data, (size_t)size);
520
  if (status != JXL_DEC_SUCCESS) {
521
    chroma_log("ERROR", "Failed to set JPEG XL input: %s", path);
522
    JxlDecoderDestroy(dec);
523
    free(jxl_data);
524
    return CHROMA_ERROR_IMAGE;
525
  }
526
527
  status =
528
      JxlDecoderSubscribeEvents(dec, JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE);
529
  if (status != JXL_DEC_SUCCESS) {
530
    JxlDecoderDestroy(dec);
531
    free(jxl_data);
532
    return CHROMA_ERROR_IMAGE;
533
  }
534
535
  JxlBasicInfo info;
536
  memset(&info, 0, sizeof(info));
537
  status = JxlDecoderProcessInput(dec);
538
  if (status != JXL_DEC_BASIC_INFO) {
539
    chroma_log("ERROR", "Failed to get JPEG XL basic info: %s", path);
540
    JxlDecoderDestroy(dec);
541
    free(jxl_data);
542
    return CHROMA_ERROR_IMAGE;
543
  }
544
545
  status = JxlDecoderGetBasicInfo(dec, &info);
546
  if (status != JXL_DEC_SUCCESS) {
547
    chroma_log("ERROR", "Failed to parse JPEG XL info: %s", path);
548
    JxlDecoderDestroy(dec);
549
    free(jxl_data);
550
    return CHROMA_ERROR_IMAGE;
551
  }
552
553
  int w = (int)info.xsize;
554
  int h = (int)info.ysize;
555
556
  size_t buffer_size = (size_t)w * (size_t)h * 4;
557
  unsigned char *data = malloc(buffer_size);
558
  if (!data) {
559
    JxlDecoderDestroy(dec);
560
    free(jxl_data);
561
    return CHROMA_ERROR_MEMORY;
562
  }
563
564
  JxlPixelFormat format = {4, JXL_TYPE_UINT8, JXL_NATIVE_ENDIAN, 0};
565
566
  status = JxlDecoderProcessInput(dec);
567
  if (status != JXL_DEC_NEED_IMAGE_OUT_BUFFER) {
568
    chroma_log("ERROR", "Unexpected JPEG XL decoder state: %s", path);
569
    free(data);
570
    JxlDecoderDestroy(dec);
571
    free(jxl_data);
572
    return CHROMA_ERROR_IMAGE;
573
  }
574
575
  status = JxlDecoderSetImageOutBuffer(dec, &format, data, buffer_size);
576
  if (status != JXL_DEC_SUCCESS) {
577
    chroma_log("ERROR", "Failed to set JPEG XL output buffer: %s", path);
578
    free(data);
579
    JxlDecoderDestroy(dec);
580
    free(jxl_data);
581
    return CHROMA_ERROR_IMAGE;
582
  }
583
584
  status = JxlDecoderProcessInput(dec);
585
  if (status != JXL_DEC_FULL_IMAGE) {
586
    chroma_log("ERROR", "Failed to decode JPEG XL image: %s", path);
587
    free(data);
588
    JxlDecoderDestroy(dec);
589
    free(jxl_data);
590
    return CHROMA_ERROR_IMAGE;
591
  }
592
593
  JxlDecoderDestroy(dec);
594
  free(jxl_data);
595
596
  memset(image, 0, sizeof(chroma_image_t));
597
  strncpy(image->path, path, MAX_PATH_LEN - 1);
598
  image->data = data;
599
  image->width = w;
600
  image->height = h;
601
  image->channels = 4;
602
  image->loaded = true;
603
  image->data_from_stbi = false;
604
  image->ref_count = 1;
605
606
  chroma_log("INFO", "Loaded JPEG XL image: %s (%dx%d)", path, w, h);
607
  return CHROMA_OK;
608
}
609
610
#else
611
612
int chroma_image_load_jpegxl(chroma_image_t *image, const char *path) {
613
  (void)image;
614
  (void)path;
615
  chroma_log("ERROR", "JPEG XL support requires libjxl. Rebuild with: "
616
                      "make CHROMA_FEATURES=jpegxl");
617
  return CHROMA_ERROR_IMAGE;
618
}
619
620
#endif