1// nonstd.h
2// A collection of useful functions and macros.
3// This library is licensed under the BSD 2-Clause License.
4//
5// This file provides both the interface and the implementation.
6// To instantiate the implementation,
7// #define NONSTD_IMPLEMENTATION
8// before including this file.
9
10#ifdef NONSTD_IMPLEMENTATION
11#ifndef _POSIX_C_SOURCE
12#define _POSIX_C_SOURCE 200809L
13#endif
14#endif
15
16#ifndef NONSTD_H
17#define NONSTD_H
18
19#include <stdarg.h>
20#include <stddef.h>
21#include <stdint.h>
22#include <stdio.h>
23#include <stdlib.h>
24#include <string.h>
25#include <sys/time.h>
26#include <time.h>
27#include <unistd.h>
28
29#ifndef NONSTD_DEF
30#ifdef NONSTD_STATIC
31#define NONSTD_DEF static
32#else
33#define NONSTD_DEF extern
34#endif
35#endif
36
37typedef int8_t i8;
38typedef uint8_t u8;
39typedef int16_t i16;
40typedef uint16_t u16;
41typedef int32_t i32;
42typedef uint32_t u32;
43typedef int64_t i64;
44typedef uint64_t u64;
45typedef unsigned int uint;
46typedef unsigned long ulong;
47typedef intptr_t isize;
48typedef uintptr_t usize;
49typedef char c8;
50
51#define countof(a) (sizeof(a) / sizeof((a)[0]))
52
53#define static_foreach(type, var, array) \
54 for (size_t _i_##var = 0, _n_##var = countof(array); \
55 _i_##var < _n_##var && ((var) = (array)[_i_##var], 1); ++_i_##var)
56
57#define ALLOC(type, n) ((type *)safe_malloc(sizeof(type), (n)))
58#define REALLOC(ptr, type, n) ((type *)safe_realloc((ptr), sizeof(type), (n)))
59#define FREE(ptr) \
60 do { \
61 free(ptr); \
62 ptr = NULL; \
63 } while (0)
64
65NONSTD_DEF void *safe_malloc(size_t item_size, size_t count);
66NONSTD_DEF void *safe_realloc(void *ptr, size_t item_size, size_t count);
67
68#define MIN(a, b) ((a) < (b) ? (a) : (b))
69#define MAX(a, b) ((a) > (b) ? (a) : (b))
70#define CLAMP(x, lo, hi) (MIN((hi), MAX((lo), (x))))
71
72// From https://github.com/tsoding/nob.h/blob/e2c9a46f01d052ab740140e74453665dc3334832/nob.h#L205-L206.
73#define UNUSED(value) (void)(value)
74#define TODO(message) \
75 do { \
76 fprintf(stderr, "%s:%d: TODO: %s\n", __FILE__, __LINE__, message); \
77 abort(); \
78 } while (0)
79#define UNREACHABLE(message) \
80 do { \
81 fprintf(stderr, "%s:%d: UNREACHABLE: %s\n", __FILE__, __LINE__, message); \
82 abort(); \
83 } while (0)
84
85#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
86#define STATIC_ASSERT(expr, msg) _Static_assert((expr), msg)
87#else
88#define STATIC_ASSERT(expr, msg) \
89 typedef char static_assertion_##msg[(expr) ? 1 : -1]
90#endif
91
92// String view - read-only, non-owning reference to a string
93typedef struct {
94 const char *data;
95 size_t length;
96} stringv;
97
98NONSTD_DEF stringv sv_from_cstr(const char *s);
99NONSTD_DEF stringv sv_from_parts(const char *data, size_t length);
100NONSTD_DEF stringv sv_slice(stringv sv, size_t start, size_t end);
101NONSTD_DEF int sv_equals(stringv a, stringv b);
102NONSTD_DEF int sv_starts_with(stringv sv, stringv prefix);
103NONSTD_DEF int sv_ends_with(stringv sv, stringv suffix);
104
105// String builder - owning, mutable, dynamically growing string buffer
106typedef struct {
107 char *data;
108 size_t length;
109 size_t capacity;
110} stringb;
111
112NONSTD_DEF void sb_init(stringb *sb, size_t initial_cap);
113NONSTD_DEF void sb_free(stringb *sb);
114NONSTD_DEF void sb_ensure(stringb *sb, size_t additional);
115NONSTD_DEF void sb_append_cstr(stringb *sb, const char *s);
116NONSTD_DEF void sb_append_sv(stringb *sb, stringv sv);
117NONSTD_DEF void sb_append_char(stringb *sb, char c);
118NONSTD_DEF stringv sb_as_sv(const stringb *sb);
119
120// Slice - generic non-owning view into an array
121// Usage: SLICE_DEF(int); slice(int) view = ...;
122#define SLICE_DEF(T) \
123 typedef struct { \
124 T *data; \
125 size_t length; \
126 } slice_##T
127
128#define slice(T) slice_##T
129
130#define make_slice(T, ptr, len) ((slice(T)){.data = (ptr), .length = (len)})
131
132#define array_as_slice(T, arr) \
133 ((slice(T)){.data = (arr).data, .length = (arr).length})
134
135// Dynamic array - generic type-safe growable array using macros
136// Usage: array(int) numbers; array_init(numbers);
137#define array(T) \
138 struct { \
139 T *data; \
140 size_t length; \
141 size_t capacity; \
142 }
143
144#define array_init(arr) \
145 do { \
146 (arr).capacity = 0; \
147 (arr).data = NULL; \
148 (arr).length = 0; \
149 } while (0)
150
151#define array_init_cap(arr, initial_cap) \
152 do { \
153 (arr).capacity = (initial_cap) ? (initial_cap) : 16; \
154 (arr).data = ALLOC(__typeof__(*(arr).data), (arr).capacity); \
155 (arr).length = 0; \
156 } while (0)
157
158#define array_free(arr) \
159 do { \
160 FREE((arr).data); \
161 (arr).length = 0; \
162 (arr).capacity = 0; \
163 } while (0)
164
165#define array_ensure(arr, additional) \
166 do { \
167 size_t _needed = (arr).length + (additional); \
168 if (_needed > (arr).capacity) { \
169 size_t _new_cap = (arr).capacity ? (arr).capacity : 16; \
170 while (_new_cap < _needed) { \
171 if (_new_cap > SIZE_MAX / 2) { \
172 _new_cap = SIZE_MAX; \
173 break; \
174 } \
175 _new_cap *= 2; \
176 } \
177 if (_new_cap < _needed) { /* Overflow or OOM */ \
178 break; \
179 } \
180 void *_new_data = \
181 safe_realloc((arr).data, sizeof(*(arr).data), _new_cap); \
182 if (_new_data) { \
183 (arr).data = _new_data; \
184 (arr).capacity = _new_cap; \
185 } \
186 } \
187 } while (0)
188
189#define array_push(arr, value) \
190 do { \
191 array_ensure((arr), 1); \
192 if ((arr).length < (arr).capacity) { \
193 (arr).data[(arr).length++] = (value); \
194 } \
195 } while (0)
196
197#define array_pop(arr) ((arr).length > 0 ? (arr).data[--(arr).length] : 0)
198
199#define array_get(arr, index) ((arr).data[index])
200
201#define array_set(arr, index, value) \
202 do { \
203 if ((index) < (arr).length) { \
204 (arr).data[index] = (value); \
205 } \
206 } while (0)
207
208#define array_insert(arr, index, value) \
209 do { \
210 if ((index) <= (arr).length) { \
211 array_ensure((arr), 1); \
212 if ((arr).length < (arr).capacity) { \
213 for (size_t _i = (arr).length; _i > (index); --_i) { \
214 (arr).data[_i] = (arr).data[_i - 1]; \
215 } \
216 (arr).data[index] = (value); \
217 (arr).length++; \
218 } \
219 } \
220 } while (0)
221
222#define array_remove(arr, index) \
223 do { \
224 if ((index) < (arr).length) { \
225 for (size_t _i = (index); _i < (arr).length - 1; ++_i) { \
226 (arr).data[_i] = (arr).data[_i + 1]; \
227 } \
228 (arr).length--; \
229 } \
230 } while (0)
231
232#define array_clear(arr) \
233 do { \
234 (arr).length = 0; \
235 } while (0)
236
237#define array_reserve(arr, new_capacity) \
238 do { \
239 if ((new_capacity) > (arr).capacity) { \
240 void *_new_data = \
241 safe_realloc((arr).data, sizeof(*(arr).data), (new_capacity)); \
242 if (_new_data) { \
243 (arr).data = _new_data; \
244 (arr).capacity = (new_capacity); \
245 } \
246 } \
247 } while (0)
248
249#define array_foreach(arr, var) \
250 for (size_t _i_##var = 0; \
251 _i_##var < (arr).length && ((var) = (arr).data[_i_##var], 1); \
252 ++_i_##var)
253
254#define array_foreach_idx(arr, var, index) \
255 for (size_t index = 0; \
256 index < (arr).length && ((var) = (arr).data[index], 1); ++index)
257
258// Arena - block-based memory allocator
259typedef struct {
260 char *ptr;
261 char *end;
262 array(char *) blocks;
263} Arena;
264
265#define ARENA_DEFAULT_BLOCK_SIZE (4096)
266
267NONSTD_DEF Arena arena_make(void);
268NONSTD_DEF void arena_grow(Arena *a, size_t min_size);
269NONSTD_DEF void *arena_alloc(Arena *a, size_t size);
270NONSTD_DEF void arena_free(Arena *a);
271
272// Image - simple RGB image structure
273typedef struct {
274 u8 r, g, b;
275} Color;
276
277typedef struct {
278 u32 width;
279 u32 height;
280 Color *pixels;
281} Canvas;
282
283#define COLOR_RGB(r, g, b) ((Color){(u8)(r), (u8)(g), (u8)(b)})
284#define COLOR_HEX(hex) ((Color){(u8)(((hex) >> 16) & 0xFF), (u8)(((hex) >> 8) & 0xFF), (u8)((hex) & 0xFF)})
285
286#define COLOR_BLACK COLOR_RGB(0, 0, 0)
287#define COLOR_WHITE COLOR_RGB(255, 255, 255)
288#define COLOR_RED COLOR_RGB(255, 0, 0)
289#define COLOR_GREEN COLOR_RGB(0, 255, 0)
290#define COLOR_BLUE COLOR_RGB(0, 0, 255)
291#define COLOR_YELLOW COLOR_RGB(255, 255, 0)
292#define COLOR_MAGENTA COLOR_RGB(255, 0, 255)
293#define COLOR_CYAN COLOR_RGB(0, 255, 255)
294
295NONSTD_DEF Canvas ppm_init(u32 width, u32 height);
296NONSTD_DEF void ppm_free(Canvas *img);
297NONSTD_DEF void ppm_set_pixel(Canvas *img, u32 x, u32 y, Color color);
298NONSTD_DEF Color ppm_get_pixel(const Canvas *img, u32 x, u32 y);
299NONSTD_DEF int ppm_save(const Canvas *img, const char *filename);
300NONSTD_DEF Canvas ppm_read(const char *filename);
301NONSTD_DEF void ppm_fill(Canvas *canvas, Color color);
302NONSTD_DEF void ppm_draw_rect(Canvas *canvas, u32 x, u32 y, u32 w, u32 h, Color color);
303NONSTD_DEF void ppm_draw_line(Canvas *canvas, i32 x0, i32 y0, i32 x1, i32 y1, Color color);
304NONSTD_DEF void ppm_draw_circle(Canvas *canvas, i32 x, i32 y, i32 r, Color color);
305NONSTD_DEF void ppm_draw_triangle(Canvas *canvas, i32 x0, i32 y0, i32 x1, i32 y1, i32 x2, i32 y2, Color color);
306
307// File I/O helpers
308NONSTD_DEF char *read_entire_file(const char *filepath, size_t *out_size);
309NONSTD_DEF int write_entire_file(const char *filepath, const void *data, size_t size);
310NONSTD_DEF stringb read_entire_file_sb(const char *filepath);
311NONSTD_DEF int write_file_sv(const char *filepath, stringv sv);
312NONSTD_DEF int write_file_sb(const char *filepath, const stringb *sb);
313
314// Logging
315typedef enum {
316 LOG_ERROR,
317 LOG_WARN,
318 LOG_INFO,
319 LOG_DEBUG,
320} LogLevel;
321
322NONSTD_DEF void set_log_level(LogLevel level);
323NONSTD_DEF LogLevel get_log_level_from_env(void);
324NONSTD_DEF void log_message(FILE *stream, LogLevel level, const char *format, ...);
325
326#define LOG_INFO_MSG(...) log_message(stdout, LOG_INFO, __VA_ARGS__)
327#define LOG_DEBUG_MSG(...) log_message(stdout, LOG_DEBUG, __VA_ARGS__)
328#define LOG_WARN_MSG(...) log_message(stderr, LOG_WARN, __VA_ARGS__)
329#define LOG_ERROR_MSG(...) log_message(stderr, LOG_ERROR, __VA_ARGS__)
330
331#define COLOR_RESET "\033[0m"
332#define COLOR_INFO "\033[32m"
333#define COLOR_DEBUG "\033[36m"
334#define COLOR_WARNING "\033[33m"
335#define COLOR_ERROR "\033[31m"
336
337#ifdef NONSTD_IMPLEMENTATION
338
339NONSTD_DEF void *safe_malloc(size_t item_size, size_t count) {
340 if (count != 0 && item_size > SIZE_MAX / count) {
341 return NULL;
342 }
343 return malloc(item_size * count);
344}
345
346NONSTD_DEF void *safe_realloc(void *ptr, size_t item_size, size_t count) {
347 if (count != 0 && item_size > SIZE_MAX / count) {
348 return NULL;
349 }
350 return realloc(ptr, item_size * count);
351}
352
353// String View Implementation
354
355NONSTD_DEF stringv sv_from_cstr(const char *s) {
356 return (stringv){.data = s, .length = s ? strlen(s) : 0};
357}
358
359NONSTD_DEF stringv sv_from_parts(const char *data, size_t length) {
360 return (stringv){.data = data, .length = length};
361}
362
363NONSTD_DEF stringv sv_slice(stringv sv, size_t start, size_t end) {
364 if (start > sv.length) {
365 start = sv.length;
366 }
367 if (end > sv.length) {
368 end = sv.length;
369 }
370 if (start > end) {
371 start = end;
372 }
373 return (stringv){.data = sv.data + start, .length = end - start};
374}
375
376NONSTD_DEF int sv_equals(stringv a, stringv b) {
377 return a.length == b.length && (a.length == 0 || memcmp(a.data, b.data, a.length) == 0);
378}
379
380NONSTD_DEF int sv_starts_with(stringv sv, stringv prefix) {
381 return sv.length >= prefix.length && memcmp(sv.data, prefix.data, prefix.length) == 0;
382}
383
384NONSTD_DEF int sv_ends_with(stringv sv, stringv suffix) {
385 return sv.length >= suffix.length && memcmp(sv.data + sv.length - suffix.length, suffix.data, suffix.length) == 0;
386}
387
388// String Builder Implementation
389
390NONSTD_DEF void sb_init(stringb *sb, size_t initial_cap) {
391 sb->capacity = initial_cap ? initial_cap : 16;
392 sb->data = ALLOC(char, sb->capacity);
393 sb->length = 0;
394 if (sb->data) {
395 sb->data[0] = '\0';
396 }
397}
398
399NONSTD_DEF void sb_free(stringb *sb) {
400 FREE(sb->data);
401 sb->length = 0;
402 sb->capacity = 0;
403}
404
405NONSTD_DEF void sb_ensure(stringb *sb, size_t additional) {
406 size_t needed = sb->length + additional + 1;
407 size_t new_cap = sb->capacity;
408
409 if (needed > new_cap) {
410 while (new_cap < needed) {
411 if (new_cap > SIZE_MAX / 2) {
412 new_cap = SIZE_MAX;
413 break;
414 }
415 new_cap *= 2;
416 }
417 if (new_cap < needed)
418 return; // Overflow
419
420 char *new_data = safe_realloc(sb->data, sizeof(char), new_cap);
421 if (new_data) {
422 sb->data = new_data;
423 sb->capacity = new_cap;
424 }
425 }
426}
427
428NONSTD_DEF void sb_append_cstr(stringb *sb, const char *s) {
429 if (!s) {
430 return;
431 }
432 size_t slength = strlen(s);
433 sb_ensure(sb, slength);
434 if (sb->length + slength + 1 <= sb->capacity) {
435 memcpy(sb->data + sb->length, s, slength);
436 sb->length += slength;
437 sb->data[sb->length] = '\0';
438 }
439}
440
441NONSTD_DEF void sb_append_sv(stringb *sb, stringv sv) {
442 if (!sv.data || sv.length == 0) {
443 return;
444 }
445 sb_ensure(sb, sv.length);
446 if (sb->length + sv.length + 1 <= sb->capacity) {
447 memcpy(sb->data + sb->length, sv.data, sv.length);
448 sb->length += sv.length;
449 sb->data[sb->length] = '\0';
450 }
451}
452
453NONSTD_DEF void sb_append_char(stringb *sb, char c) {
454 sb_ensure(sb, 1);
455 if (sb->length + 2 <= sb->capacity) {
456 sb->data[sb->length++] = c;
457 sb->data[sb->length] = '\0';
458 }
459}
460
461NONSTD_DEF stringv sb_as_sv(const stringb *sb) {
462 return (stringv){.data = sb->data, .length = sb->length};
463}
464
465NONSTD_DEF Arena arena_make(void) {
466 Arena a = {0};
467 array_init(a.blocks);
468 return a;
469}
470
471// Arena Implementation
472
473NONSTD_DEF void arena_grow(Arena *a, size_t min_size) {
474 size_t size = MAX(ARENA_DEFAULT_BLOCK_SIZE, min_size);
475 char *block = ALLOC(char, size);
476 a->ptr = block;
477 a->end = block + size;
478 array_push(a->blocks, block);
479}
480
481NONSTD_DEF void *arena_alloc(Arena *a, size_t size) {
482 // Align to 8 bytes basically
483 size_t align = sizeof(void *);
484 uintptr_t current = (uintptr_t)a->ptr;
485 uintptr_t aligned = (current + align - 1) & ~(align - 1);
486 uintptr_t end = (uintptr_t)a->end;
487
488 // Check for overflow (aligned wrapped around) or out of bounds (aligned >= end)
489 // or not enough space ((end - aligned) < size)
490 if (aligned < current || aligned >= end || (end - aligned) < size) {
491 arena_grow(a, size);
492 current = (uintptr_t)a->ptr;
493 aligned = (current + align - 1) & ~(align - 1);
494 end = (uintptr_t)a->end;
495 }
496
497 // Double check after grow (in case grow failed or size is just too huge)
498 if (aligned < current || aligned >= end || (end - aligned) < size) {
499 return NULL;
500 }
501
502 a->ptr = (char *)(aligned + size);
503 return (void *)aligned;
504}
505
506NONSTD_DEF void arena_free(Arena *a) {
507 char *block;
508 array_foreach(a->blocks, block) { FREE(block); }
509 array_free(a->blocks);
510 a->ptr = NULL;
511 a->end = NULL;
512}
513
514// File I/O Implementation
515
516NONSTD_DEF char *read_entire_file(const char *filepath, size_t *out_size) {
517 FILE *f = fopen(filepath, "rb");
518 if (!f) {
519 return NULL;
520 }
521
522 fseek(f, 0, SEEK_END);
523 long length = ftell(f);
524 fseek(f, 0, SEEK_SET);
525
526 if (length < 0) {
527 fclose(f);
528 return NULL;
529 }
530
531 size_t size = (size_t)length;
532 char *buffer = ALLOC(char, size + 1);
533 if (!buffer) {
534 fclose(f);
535 return NULL;
536 }
537
538 size_t read = fread(buffer, 1, size, f);
539 fclose(f);
540
541 if (read != size) {
542 FREE(buffer);
543 return NULL;
544 }
545
546 buffer[size] = '\0';
547 if (out_size) {
548 *out_size = size;
549 }
550
551 return buffer;
552}
553
554NONSTD_DEF int write_entire_file(const char *filepath, const void *data, size_t size) {
555 FILE *f = fopen(filepath, "wb");
556 if (!f) {
557 return 0;
558 }
559
560 size_t written = fwrite(data, 1, size, f);
561 fclose(f);
562
563 return written == size;
564}
565
566NONSTD_DEF stringb read_entire_file_sb(const char *filepath) {
567 size_t size = 0;
568 char *data = read_entire_file(filepath, &size);
569 stringb sb = {0};
570 if (data) {
571 sb.data = data;
572 sb.length = size;
573 sb.capacity = size + 1;
574 }
575 return sb;
576}
577
578NONSTD_DEF int write_file_sv(const char *filepath, stringv sv) {
579 return write_entire_file(filepath, sv.data, sv.length);
580}
581
582NONSTD_DEF int write_file_sb(const char *filepath, const stringb *sb) {
583 return write_entire_file(filepath, sb->data, sb->length);
584}
585
586// Logging Implementation
587
588static LogLevel max_level = LOG_INFO;
589
590static const char *level_strings[] = {
591 "ERROR",
592 "WARN",
593 "INFO",
594 "DEBUG",
595};
596
597static const char *level_colors[] = {
598 COLOR_ERROR,
599 COLOR_WARNING,
600 COLOR_INFO,
601 COLOR_DEBUG,
602};
603
604NONSTD_DEF void set_log_level(LogLevel level) {
605 max_level = level;
606}
607
608NONSTD_DEF LogLevel get_log_level_from_env(void) {
609 const char *env = getenv("LOG_LEVEL");
610 if (env) {
611 int level = atoi(env);
612 if (level >= 0 && level <= 3) {
613 return (LogLevel)level;
614 }
615 }
616
617 return max_level;
618}
619
620NONSTD_DEF void log_message(FILE *stream, LogLevel level, const char *format, ...) {
621 if (max_level < level)
622 return;
623
624 struct timeval tv;
625 gettimeofday(&tv, NULL);
626 struct tm *tm_info = localtime(&tv.tv_sec);
627
628 char time_str[24];
629 strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S", tm_info);
630
631 const char *color = isatty(fileno(stream)) ? level_colors[level] : "";
632 const char *reset = isatty(fileno(stream)) ? COLOR_RESET : "";
633
634 const char *log_format = "%s[%s.%03d] [%-5s] ";
635 fprintf(stream, log_format, color, time_str, (int)(tv.tv_usec / 1000), level_strings[level]);
636
637 va_list args;
638 va_start(args, format);
639 vfprintf(stream, format, args);
640 va_end(args);
641
642 fprintf(stream, "%s\n", reset);
643 fflush(stream);
644}
645
646// PPM Image Implementation
647
648NONSTD_DEF Canvas ppm_init(u32 width, u32 height) {
649 Canvas img = {0};
650 img.width = width;
651 img.height = height;
652 img.pixels = ALLOC(Color, width * height);
653 if (img.pixels) {
654 memset(img.pixels, 0, sizeof(Color) * width * height);
655 }
656 return img;
657}
658
659NONSTD_DEF void ppm_free(Canvas *img) {
660 if (img->pixels) {
661 FREE(img->pixels);
662 }
663 img->width = 0;
664 img->height = 0;
665}
666
667NONSTD_DEF void ppm_set_pixel(Canvas *img, u32 x, u32 y, Color color) {
668 if (x < img->width && y < img->height) {
669 img->pixels[y * img->width + x] = color;
670 }
671}
672
673NONSTD_DEF Color ppm_get_pixel(const Canvas *img, u32 x, u32 y) {
674 if (x < img->width && y < img->height) {
675 return img->pixels[y * img->width + x];
676 }
677 return (Color){0, 0, 0};
678}
679
680NONSTD_DEF int ppm_save(const Canvas *img, const char *filename) {
681 FILE *f = fopen(filename, "w");
682 if (!f) {
683 return 0;
684 }
685
686 fprintf(f, "P3\n%u %u\n255\n", img->width, img->height);
687 for (u32 y = 0; y < img->height; ++y) {
688 for (u32 x = 0; x < img->width; ++x) {
689 Color c = ppm_get_pixel(img, x, y);
690 fprintf(f, "%d %d %d ", c.r, c.g, c.b);
691 }
692 fprintf(f, "\n");
693 }
694
695 fclose(f);
696 return 1;
697}
698
699NONSTD_DEF Canvas ppm_read(const char *filename) {
700 Canvas img = {0};
701 FILE *f = fopen(filename, "r");
702 if (!f) {
703 return img;
704 }
705
706 char magic[3];
707 if (fscanf(f, "%2s", magic) != 1 || strcmp(magic, "P3") != 0) {
708 fclose(f);
709 return img;
710 }
711
712 u32 w, h, max_val;
713 if (fscanf(f, "%u %u %u", &w, &h, &max_val) != 3) {
714 fclose(f);
715 return img;
716 }
717
718 img = ppm_init(w, h);
719 if (!img.pixels) {
720 fclose(f);
721 return img;
722 }
723
724 for (u32 i = 0; i < w * h; ++i) {
725 int r, g, b;
726 if (fscanf(f, "%d %d %d", &r, &g, &b) != 3) {
727 ppm_free(&img);
728 fclose(f);
729 return (Canvas){0};
730 }
731 img.pixels[i] = (Color){(u8)r, (u8)g, (u8)b};
732 }
733
734 fclose(f);
735 return img;
736}
737
738NONSTD_DEF void ppm_fill(Canvas *canvas, Color color) {
739 for (u32 i = 0; i < canvas->width * canvas->height; ++i) {
740 canvas->pixels[i] = color;
741 }
742}
743
744NONSTD_DEF void ppm_draw_rect(Canvas *canvas, u32 x, u32 y, u32 w, u32 h, Color color) {
745 if (w == 0 || h == 0) {
746 return;
747 }
748 for (u32 i = x; i < x + w; ++i) {
749 ppm_set_pixel(canvas, i, y, color);
750 ppm_set_pixel(canvas, i, y + h - 1, color);
751 }
752 for (u32 j = y; j < y + h; ++j) {
753 ppm_set_pixel(canvas, x, j, color);
754 ppm_set_pixel(canvas, x + w - 1, j, color);
755 }
756}
757
758NONSTD_DEF void ppm_draw_line(Canvas *canvas, i32 x0, i32 y0, i32 x1, i32 y1, Color color) {
759 i32 dx = abs(x1 - x0);
760 i32 dy = -abs(y1 - y0);
761 i32 sx = x0 < x1 ? 1 : -1;
762 i32 sy = y0 < y1 ? 1 : -1;
763 i32 err = dx + dy;
764
765 while (1) {
766 ppm_set_pixel(canvas, (u32)x0, (u32)y0, color);
767 if (x0 == x1 && y0 == y1) {
768 break;
769 }
770
771 i32 e2 = 2 * err;
772 if (e2 >= dy) {
773 err += dy;
774 x0 += sx;
775 }
776 if (e2 <= dx) {
777 err += dx;
778 y0 += sy;
779 }
780 }
781}
782
783NONSTD_DEF void ppm_draw_circle(Canvas *canvas, i32 xm, i32 ym, i32 r, Color color) {
784 i32 x = -r, y = 0, err = 2 - 2 * r;
785 do {
786 ppm_set_pixel(canvas, (u32)(xm - x), (u32)(ym + y), color);
787 ppm_set_pixel(canvas, (u32)(xm - y), (u32)(ym - x), color);
788 ppm_set_pixel(canvas, (u32)(xm + x), (u32)(ym - y), color);
789 ppm_set_pixel(canvas, (u32)(xm + y), (u32)(ym + x), color);
790 r = err;
791 if (r <= y) {
792 err += ++y * 2 + 1;
793 }
794 if (r > x || err > y) {
795 err += ++x * 2 + 1;
796 }
797 } while (x < 0);
798}
799
800NONSTD_DEF void ppm_draw_triangle(Canvas *canvas, i32 x0, i32 y0, i32 x1, i32 y1, i32 x2, i32 y2, Color color) {
801 ppm_draw_line(canvas, x0, y0, x1, y1, color);
802 ppm_draw_line(canvas, x1, y1, x2, y2, color);
803 ppm_draw_line(canvas, x2, y2, x0, y0, color);
804}
805
806#endif // NONSTD_IMPLEMENTATION
807
808#endif // NONSTD_H
809
810/*
811BSD 2-Clause License
812
813Copyright (c) 2026, Mitja Felicijan <mitja.felicijan@gmail.com>
814
815Redistribution and use in source and binary forms, with or without
816modification, are permitted provided that the following conditions are met:
817
8181. Redistributions of source code must retain the above copyright notice, this
819 list of conditions and the following disclaimer.
820
8212. Redistributions in binary form must reproduce the above copyright notice,
822 this list of conditions and the following disclaimer in the documentation
823 and/or other materials provided with the distribution.
824
825THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
826AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
827IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
828DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
829FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
830DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
831SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
832CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
833OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
834OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
835*/