summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--examples/.gitignore4
-rw-r--r--examples/Makefile7
-rw-r--r--examples/logging.c32
-rw-r--r--examples/ppm.c29
-rw-r--r--nonstd.h209
-rw-r--r--tests.c97
6 files changed, 355 insertions, 23 deletions
diff --git a/examples/.gitignore b/examples/.gitignore
index dcb0ae9..b4d84dd 100644
--- a/examples/.gitignore
+++ b/examples/.gitignore
@@ -1,3 +1,4 @@
+# Build artifacts
stringv
stringb
foreach
@@ -6,4 +7,7 @@ slice
arena
files
logging
+ppm
+# Generated artifacts
+*.ppm \ No newline at end of file
diff --git a/examples/Makefile b/examples/Makefile
index cd83e92..50d531b 100644
--- a/examples/Makefile
+++ b/examples/Makefile
@@ -5,7 +5,7 @@ CFLAGS = -Wall -Wextra -std=c99 -fsanitize=address -g -O0
LDFLAGS =
# Example targets
-EXAMPLES = foreach stringv stringb array slice arena files logging
+EXAMPLES = foreach stringv stringb array slice arena files logging ppm
# Default target
all: $(EXAMPLES)
@@ -35,6 +35,9 @@ files: files.c
logging: logging.c
$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
+ppm: ppm.c
+ $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
+
# Run all examples
run: all
@echo "\n=== Running stringv ===\n"
@@ -53,6 +56,8 @@ run: all
@./files
@echo "\n=== Running logging ===\n"
@./logging
+ @echo "\n=== Running ppm ===\n"
+ @./ppm
# Clean build artifacts
clean:
diff --git a/examples/logging.c b/examples/logging.c
index 1d2744c..8e87d59 100644
--- a/examples/logging.c
+++ b/examples/logging.c
@@ -2,24 +2,24 @@
#include "../nonstd.h"
int main(void) {
- // Default level is LOG_INFO
- LOG_INFO_MSG("This is an info message: %d", 42);
- LOG_DEBUG_MSG("This debug message will NOT be shown by default");
+ // Default level is LOG_INFO
+ LOG_INFO_MSG("This is an info message: %d", 42);
+ LOG_DEBUG_MSG("This debug message will NOT be shown by default");
- // Change level to LOG_DEBUG
- set_log_level(LOG_DEBUG);
- LOG_DEBUG_MSG("Now debug messages are shown: %s", "hello");
+ // Change level to LOG_DEBUG
+ set_log_level(LOG_DEBUG);
+ LOG_DEBUG_MSG("Now debug messages are shown: %s", "hello");
- // Warnings and Errors
- LOG_WARN_MSG("This is a warning!");
- LOG_ERROR_MSG("This is an error!");
+ // Warnings and Errors
+ LOG_WARN_MSG("This is a warning!");
+ LOG_ERROR_MSG("This is an error!");
- // Environment variable override test
- // You can set LOG_LEVEL=1 (WARN) etc.
- LogLevel env_level = get_log_level_from_env();
- if (env_level != LOG_DEBUG) {
- printf("Environment overrides level to: %d\n", env_level);
- }
+ // Environment variable override test
+ // You can set LOG_LEVEL=1 (WARN) etc.
+ LogLevel env_level = get_log_level_from_env();
+ if (env_level != LOG_DEBUG) {
+ printf("Environment overrides level to: %d\n", env_level);
+ }
- return 0;
+ return 0;
}
diff --git a/examples/ppm.c b/examples/ppm.c
new file mode 100644
index 0000000..95fafcf
--- /dev/null
+++ b/examples/ppm.c
@@ -0,0 +1,29 @@
+#define NONSTD_IMPLEMENTATION
+#include "../nonstd.h"
+
+int main() {
+ u32 width = 400;
+ u32 height = 400;
+ Canvas img = ppm_init(width, height);
+
+ // Background
+ ppm_fill(&img, COLOR_HEX(0x1a1a1a));
+
+ // Draw some shapes
+ ppm_draw_rect(&img, 50, 50, 100, 100, CLR_RED);
+ ppm_draw_circle(&img, 250, 100, 40, CLR_BLUE);
+ ppm_draw_triangle(&img, 50, 350, 150, 350, 100, 250, CLR_YELLOW);
+ ppm_draw_line(&img, 200, 200, 350, 350, CLR_GREEN);
+
+ // Random colors and macros
+ ppm_draw_rect(&img, 200, 250, 50, 80, COLOR_RGB(255, 165, 0));
+
+ if (ppm_save(&img, "example.ppm")) {
+ printf("Image saved to example.ppm\n");
+ } else {
+ printf("Failed to save image\n");
+ }
+
+ ppm_free(&img);
+ return 0;
+}
diff --git a/nonstd.h b/nonstd.h
index b826dfb..efe10af 100644
--- a/nonstd.h
+++ b/nonstd.h
@@ -7,14 +7,14 @@
#ifndef NONSTD_H
#define NONSTD_H
+#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <stdarg.h>
-#include <time.h>
#include <sys/time.h>
+#include <time.h>
#include <unistd.h>
#ifndef NONSTD_DEF
@@ -249,6 +249,41 @@ NONSTD_DEF void arena_grow(Arena *a, size_t min_size);
NONSTD_DEF void *arena_alloc(Arena *a, size_t size);
NONSTD_DEF void arena_free(Arena *a);
+// Image - simple RGB image structure
+typedef struct {
+ u8 r, g, b;
+} Color;
+
+typedef struct {
+ u32 width;
+ u32 height;
+ Color *pixels;
+} Canvas;
+
+#define COLOR_RGB(r, g, b) ((Color){(u8)(r), (u8)(g), (u8)(b)})
+#define COLOR_HEX(hex) ((Color){(u8)(((hex) >> 16) & 0xFF), (u8)(((hex) >> 8) & 0xFF), (u8)((hex) & 0xFF)})
+
+#define CLR_BLACK COLOR_RGB(0, 0, 0)
+#define CLR_WHITE COLOR_RGB(255, 255, 255)
+#define CLR_RED COLOR_RGB(255, 0, 0)
+#define CLR_GREEN COLOR_RGB(0, 255, 0)
+#define CLR_BLUE COLOR_RGB(0, 0, 255)
+#define CLR_YELLOW COLOR_RGB(255, 255, 0)
+#define CLR_MAGENTA COLOR_RGB(255, 0, 255)
+#define CLR_CYAN COLOR_RGB(0, 255, 255)
+
+NONSTD_DEF Canvas ppm_init(u32 width, u32 height);
+NONSTD_DEF void ppm_free(Canvas *img);
+NONSTD_DEF void ppm_set_pixel(Canvas *img, u32 x, u32 y, Color color);
+NONSTD_DEF Color ppm_get_pixel(const Canvas *img, u32 x, u32 y);
+NONSTD_DEF int ppm_save(const Canvas *img, const char *filename);
+NONSTD_DEF Canvas ppm_read(const char *filename);
+NONSTD_DEF void ppm_fill(Canvas *canvas, Color color);
+NONSTD_DEF void ppm_draw_rect(Canvas *canvas, u32 x, u32 y, u32 w, u32 h, Color color);
+NONSTD_DEF void ppm_draw_line(Canvas *canvas, i32 x0, i32 y0, i32 x1, i32 y1, Color color);
+NONSTD_DEF void ppm_draw_circle(Canvas *canvas, i32 x, i32 y, i32 r, Color color);
+NONSTD_DEF void ppm_draw_triangle(Canvas *canvas, i32 x0, i32 y0, i32 x1, i32 y1, i32 x2, i32 y2, Color color);
+
// File I/O helpers
NONSTD_DEF char *read_entire_file(const char *filepath, size_t *out_size);
NONSTD_DEF int write_entire_file(const char *filepath, const void *data, size_t size);
@@ -297,6 +332,8 @@ NONSTD_DEF void *safe_realloc(void *ptr, size_t item_size, size_t count) {
return realloc(ptr, item_size * count);
}
+// String View Implementation
+
NONSTD_DEF stringv sv_from_cstr(const char *s) {
return (stringv){.data = s, .length = s ? strlen(s) : 0};
}
@@ -330,6 +367,8 @@ NONSTD_DEF int sv_ends_with(stringv sv, stringv suffix) {
return sv.length >= suffix.length && memcmp(sv.data + sv.length - suffix.length, suffix.data, suffix.length) == 0;
}
+// String Builder Implementation
+
NONSTD_DEF void sb_init(stringb *sb, size_t initial_cap) {
sb->capacity = initial_cap ? initial_cap : 16;
sb->data = ALLOC(char, sb->capacity);
@@ -411,6 +450,8 @@ NONSTD_DEF Arena arena_make(void) {
return a;
}
+// Arena Implementation
+
NONSTD_DEF void arena_grow(Arena *a, size_t min_size) {
size_t size = MAX(ARENA_DEFAULT_BLOCK_SIZE, min_size);
char *block = ALLOC(char, size);
@@ -452,6 +493,8 @@ NONSTD_DEF void arena_free(Arena *a) {
a->end = NULL;
}
+// File I/O Implementation
+
NONSTD_DEF char *read_entire_file(const char *filepath, size_t *out_size) {
FILE *f = fopen(filepath, "rb");
if (!f) {
@@ -571,8 +614,7 @@ NONSTD_DEF void log_message(FILE *stream, LogLevel level, const char *format, ..
const char *reset = isatty(fileno(stream)) ? COLOR_RESET : "";
const char *log_format = "%s[%s.%03d] [%-5s] ";
- fprintf(stream, log_format, color, time_str, (int)(tv.tv_usec / 1000),
- level_strings[level]);
+ fprintf(stream, log_format, color, time_str, (int)(tv.tv_usec / 1000), level_strings[level]);
va_list args;
va_start(args, format);
@@ -583,4 +625,163 @@ NONSTD_DEF void log_message(FILE *stream, LogLevel level, const char *format, ..
fflush(stream);
}
+// PPM Image Implementation
+
+NONSTD_DEF Canvas ppm_init(u32 width, u32 height) {
+ Canvas img = {0};
+ img.width = width;
+ img.height = height;
+ img.pixels = ALLOC(Color, width * height);
+ if (img.pixels) {
+ memset(img.pixels, 0, sizeof(Color) * width * height);
+ }
+ return img;
+}
+
+NONSTD_DEF void ppm_free(Canvas *img) {
+ if (img->pixels) {
+ FREE(img->pixels);
+ }
+ img->width = 0;
+ img->height = 0;
+}
+
+NONSTD_DEF void ppm_set_pixel(Canvas *img, u32 x, u32 y, Color color) {
+ if (x < img->width && y < img->height) {
+ img->pixels[y * img->width + x] = color;
+ }
+}
+
+NONSTD_DEF Color ppm_get_pixel(const Canvas *img, u32 x, u32 y) {
+ if (x < img->width && y < img->height) {
+ return img->pixels[y * img->width + x];
+ }
+ return (Color){0, 0, 0};
+}
+
+NONSTD_DEF int ppm_save(const Canvas *img, const char *filename) {
+ FILE *f = fopen(filename, "w");
+ if (!f) {
+ return 0;
+ }
+
+ fprintf(f, "P3\n%u %u\n255\n", img->width, img->height);
+ for (u32 y = 0; y < img->height; ++y) {
+ for (u32 x = 0; x < img->width; ++x) {
+ Color c = ppm_get_pixel(img, x, y);
+ fprintf(f, "%d %d %d ", c.r, c.g, c.b);
+ }
+ fprintf(f, "\n");
+ }
+
+ fclose(f);
+ return 1;
+}
+
+NONSTD_DEF Canvas ppm_read(const char *filename) {
+ Canvas img = {0};
+ FILE *f = fopen(filename, "r");
+ if (!f) {
+ return img;
+ }
+
+ char magic[3];
+ if (fscanf(f, "%2s", magic) != 1 || strcmp(magic, "P3") != 0) {
+ fclose(f);
+ return img;
+ }
+
+ u32 w, h, max_val;
+ if (fscanf(f, "%u %u %u", &w, &h, &max_val) != 3) {
+ fclose(f);
+ return img;
+ }
+
+ img = ppm_init(w, h);
+ if (!img.pixels) {
+ fclose(f);
+ return img;
+ }
+
+ for (u32 i = 0; i < w * h; ++i) {
+ int r, g, b;
+ if (fscanf(f, "%d %d %d", &r, &g, &b) != 3) {
+ ppm_free(&img);
+ fclose(f);
+ return (Canvas){0};
+ }
+ img.pixels[i] = (Color){(u8)r, (u8)g, (u8)b};
+ }
+
+ fclose(f);
+ return img;
+}
+
+NONSTD_DEF void ppm_fill(Canvas *canvas, Color color) {
+ for (u32 i = 0; i < canvas->width * canvas->height; ++i) {
+ canvas->pixels[i] = color;
+ }
+}
+
+NONSTD_DEF void ppm_draw_rect(Canvas *canvas, u32 x, u32 y, u32 w, u32 h, Color color) {
+ if (w == 0 || h == 0)
+ return;
+ for (u32 i = x; i < x + w; ++i) {
+ ppm_set_pixel(canvas, i, y, color);
+ ppm_set_pixel(canvas, i, y + h - 1, color);
+ }
+ for (u32 j = y; j < y + h; ++j) {
+ ppm_set_pixel(canvas, x, j, color);
+ ppm_set_pixel(canvas, x + w - 1, j, color);
+ }
+}
+
+NONSTD_DEF void ppm_draw_line(Canvas *canvas, i32 x0, i32 y0, i32 x1, i32 y1, Color color) {
+ i32 dx = abs(x1 - x0);
+ i32 dy = -abs(y1 - y0);
+ i32 sx = x0 < x1 ? 1 : -1;
+ i32 sy = y0 < y1 ? 1 : -1;
+ i32 err = dx + dy;
+
+ while (1) {
+ ppm_set_pixel(canvas, (u32)x0, (u32)y0, color);
+ if (x0 == x1 && y0 == y1) {
+ break;
+ }
+
+ i32 e2 = 2 * err;
+ if (e2 >= dy) {
+ err += dy;
+ x0 += sx;
+ }
+ if (e2 <= dx) {
+ err += dx;
+ y0 += sy;
+ }
+ }
+}
+
+NONSTD_DEF void ppm_draw_circle(Canvas *canvas, i32 xm, i32 ym, i32 r, Color color) {
+ i32 x = -r, y = 0, err = 2 - 2 * r;
+ do {
+ ppm_set_pixel(canvas, (u32)(xm - x), (u32)(ym + y), color);
+ ppm_set_pixel(canvas, (u32)(xm - y), (u32)(ym - x), color);
+ ppm_set_pixel(canvas, (u32)(xm + x), (u32)(ym - y), color);
+ ppm_set_pixel(canvas, (u32)(xm + y), (u32)(ym + x), color);
+ r = err;
+ if (r <= y) {
+ err += ++y * 2 + 1;
+ }
+ if (r > x || err > y) {
+ err += ++x * 2 + 1;
+ }
+ } while (x < 0);
+}
+
+NONSTD_DEF void ppm_draw_triangle(Canvas *canvas, i32 x0, i32 y0, i32 x1, i32 y1, i32 x2, i32 y2, Color color) {
+ ppm_draw_line(canvas, x0, y0, x1, y1, color);
+ ppm_draw_line(canvas, x1, y1, x2, y2, color);
+ ppm_draw_line(canvas, x2, y2, x0, y0, color);
+}
+
#endif // NONSTD_IMPLEMENTATION \ No newline at end of file
diff --git a/tests.c b/tests.c
index 9eb7469..bca3a89 100644
--- a/tests.c
+++ b/tests.c
@@ -864,7 +864,7 @@ MU_TEST(test_logging_level_filtering) {
set_log_level(LOG_WARN);
- log_message(tmp, LOG_INFO, "Info message"); // Should not be logged
+ log_message(tmp, LOG_INFO, "Info message"); // Should not be logged
log_message(tmp, LOG_ERROR, "Error message"); // Should be logged
rewind(tmp);
@@ -910,11 +910,95 @@ MU_TEST(test_logging_format) {
mu_check(strstr(buffer, "Test 123 format") != NULL);
mu_check(strstr(buffer, "[INFO ]") != NULL);
// Check timestamp format roughly (YYYY-MM-DD)
- mu_check(strstr(buffer, "20") != NULL);
+ mu_check(strstr(buffer, "20") != NULL);
fclose(tmp);
}
+// Image tests
+MU_TEST(test_ppm_init_free) {
+ Canvas img = ppm_init(100, 100);
+ mu_assert_int_eq(100, (int)img.width);
+ mu_assert_int_eq(100, (int)img.height);
+ mu_check(img.pixels != NULL);
+ ppm_free(&img);
+ mu_assert_int_eq(0, (int)img.width);
+ mu_assert_int_eq(0, (int)img.height);
+ mu_check(img.pixels == NULL);
+}
+
+MU_TEST(test_ppm_set_get_pixel) {
+ Canvas img = ppm_init(10, 10);
+ Color c = {255, 128, 64};
+ ppm_set_pixel(&img, 5, 5, c);
+ Color got = ppm_get_pixel(&img, 5, 5);
+ mu_assert_int_eq(255, got.r);
+ mu_assert_int_eq(128, got.g);
+ mu_assert_int_eq(64, got.b);
+
+ // Test out of bounds (should return black)
+ got = ppm_get_pixel(&img, 100, 100);
+ mu_assert_int_eq(0, got.r);
+
+ ppm_free(&img);
+}
+
+MU_TEST(test_ppm_save_read) {
+ Canvas img = ppm_init(10, 10);
+ for (u32 y = 0; y < 10; ++y) {
+ for (u32 x = 0; x < 10; ++x) {
+ ppm_set_pixel(&img, x, y, (Color){(u8)(x * 20), (u8)(y * 20), 100});
+ }
+ }
+
+ const char *tmp_ppm = "test_image.ppm";
+ mu_check(ppm_save(&img, tmp_ppm));
+
+ Canvas read = ppm_read(tmp_ppm);
+ mu_assert_int_eq((int)img.width, (int)read.width);
+ mu_assert_int_eq((int)img.height, (int)read.height);
+ mu_check(read.pixels != NULL);
+
+ for (u32 i = 0; i < 100; ++i) {
+ mu_assert_int_eq(img.pixels[i].r, read.pixels[i].r);
+ mu_assert_int_eq(img.pixels[i].g, read.pixels[i].g);
+ mu_assert_int_eq(img.pixels[i].b, read.pixels[i].b);
+ }
+
+ ppm_free(&img);
+ ppm_free(&read);
+ remove(tmp_ppm);
+}
+
+MU_TEST(test_ppm_draw_helpers) {
+ Canvas img = ppm_init(100, 100);
+
+ // Test fill
+ ppm_fill(&img, CLR_RED);
+ Color c1 = ppm_get_pixel(&img, 0, 0);
+ mu_assert_int_eq(255, c1.r);
+ mu_assert_int_eq(0, c1.g);
+
+ // Test rect
+ ppm_fill(&img, CLR_BLACK);
+ ppm_draw_rect(&img, 10, 10, 20, 20, CLR_WHITE);
+ mu_assert_int_eq(255, ppm_get_pixel(&img, 10, 10).r);
+ mu_assert_int_eq(0, ppm_get_pixel(&img, 15, 15).r); // Inside should be black
+
+ // Test line
+ ppm_fill(&img, CLR_BLACK);
+ ppm_draw_line(&img, 0, 0, 10, 10, CLR_GREEN);
+ mu_assert_int_eq(255, ppm_get_pixel(&img, 5, 5).g);
+
+ // Test hexagon/color macros
+ Color hex = COLOR_HEX(0x112233);
+ mu_assert_int_eq(0x11, hex.r);
+ mu_assert_int_eq(0x22, hex.g);
+ mu_assert_int_eq(0x33, hex.b);
+
+ ppm_free(&img);
+}
+
// Test suites
MU_TEST_SUITE(test_suite_stringv) {
printf("\n[String View Tests]\n");
@@ -1025,6 +1109,14 @@ MU_TEST_SUITE(test_suite_logging) {
RUN_TEST_WITH_NAME(test_logging_format);
}
+MU_TEST_SUITE(test_suite_image) {
+ printf("\n[Image Tests]\n");
+ RUN_TEST_WITH_NAME(test_ppm_init_free);
+ RUN_TEST_WITH_NAME(test_ppm_set_get_pixel);
+ RUN_TEST_WITH_NAME(test_ppm_save_read);
+ RUN_TEST_WITH_NAME(test_ppm_draw_helpers);
+}
+
int main(int argc, char *argv[]) {
(void)argc;
(void)argv;
@@ -1038,6 +1130,7 @@ int main(int argc, char *argv[]) {
MU_RUN_SUITE(test_suite_arena);
MU_RUN_SUITE(test_suite_files);
MU_RUN_SUITE(test_suite_logging);
+ MU_RUN_SUITE(test_suite_image);
MU_REPORT();