Better PPM parser

Author Mitja Felicijan <mitja.felicijan@gmail.com> 2026-05-11 20:17:07 +0200
Committer Mitja Felicijan <mitja.felicijan@gmail.com> 2026-05-11 20:17:07 +0200
Commit 80c3ce34fc1aef5792d1b2c8f115c04185125530 (patch)
-rw-r--r-- Makefile 8
-rw-r--r-- all.h 37
-rw-r--r-- main.c 89
-rw-r--r-- ppm.c 105
4 files changed, 153 insertions, 86 deletions
diff --git a/Makefile b/Makefile
1
CC         := clang
1
CC         := clang
2
CFLAGS     := -std=c99 -Wall -Wextra
2
CFLAGS     := -std=c99 -Wall -Wextra -ggdb
3
LDFLAGS    := -lm
3
LDFLAGS    := -lm
4
SYSTEM     := $(shell uname -s)
4
SYSTEM     := $(shell uname -s)
  
5
  
  
6
SOURCES    := main.c ppm.c
5
  
7
  
6
ifeq ($(SYSTEM), Linux)
8
ifeq ($(SYSTEM), Linux)
7
    LDFLAGS += -lGL -lglut
9
    LDFLAGS += -lGL -lglut
...
11
    LDFLAGS += -framework OpenGL -framework GLUT
13
    LDFLAGS += -framework OpenGL -framework GLUT
12
endif
14
endif
13
  
15
  
14
main: main.c
16
main: $(SOURCES)
15
	clang $(CFLAGS) main.c -o main $(LDFLAGS)
17
	clang $(CFLAGS) $(SOURCES) -o main $(LDFLAGS)
diff --git a/all.h b/all.h
  
1
#ifndef ALL_H
  
2
#define ALL_H
  
3
  
  
4
typedef struct {
  
5
	float x, y, z;
  
6
} Vec2;
  
7
  
  
8
typedef struct {
  
9
	float x, y, z;
  
10
} Vec3;
  
11
  
  
12
typedef struct {
  
13
	float x, y, z, w;
  
14
} Vec4;
  
15
  
  
16
typedef struct {
  
17
	char format[3];
  
18
	int width;
  
19
	int height;
  
20
	int color_space;
  
21
	unsigned char *pixels;
  
22
} Image;
  
23
  
  
24
#define COLOR_RED     (Vec3){ .x = 1.0f, .y = 0.0f, .z = 0.0f }
  
25
#define COLOR_GREEN   (Vec3){ .x = 0.0f, .y = 1.0f, .z = 0.0f }
  
26
#define COLOR_BLUE    (Vec3){ .x = 0.0f, .y = 0.0f, .z = 1.0f }
  
27
#define COLOR_WHITE   (Vec3){ .x = 1.0f, .y = 1.0f, .z = 1.0f }
  
28
#define COLOR_BLACK   (Vec3){ .x = 0.0f, .y = 0.0f, .z = 0.0f }
  
29
#define COLOR_YELLOW  (Vec3){ .x = 1.0f, .y = 1.0f, .z = 0.0f }
  
30
#define COLOR_CYAN    (Vec3){ .x = 0.0f, .y = 1.0f, .z = 1.0f }
  
31
#define COLOR_MAGENTA (Vec3){ .x = 1.0f, .y = 0.0f, .z = 1.0f }
  
32
#define COLOR_ORANGE  (Vec3){ .x = 1.0f, .y = 0.5f, .z = 0.0f }
  
33
#define COLOR_PURPLE  (Vec3){ .x = 0.5f, .y = 0.0f, .z = 0.5f }
  
34
  
  
35
Image read_ppm_image(const char *filename);
  
36
  
  
37
#endif
diff --git a/main.c b/main.c
...
4
// 2. Store them in a contiguous memory buffer (Batch).
4
// 2. Store them in a contiguous memory buffer (Batch).
5
// 3. Send the entire buffer to the GPU in a single call (glDrawArrays).
5
// 3. Send the entire buffer to the GPU in a single call (glDrawArrays).
6
  
6
  
  
7
#include "all.h"
  
8
  
7
#include <stdio.h>
9
#include <stdio.h>
8
#include <stdlib.h>
10
#include <stdlib.h>
9
#include <getopt.h>
11
#include <getopt.h>
...
25
    #include <GL/gl.h>
27
    #include <GL/gl.h>
26
    #include <GL/glut.h>
28
    #include <GL/glut.h>
27
#endif
29
#endif
28
  
  
29
typedef struct {
  
30
	float x, y, z;
  
31
} Vec2;
  
32
  
  
33
typedef struct {
  
34
	float x, y, z;
  
35
} Vec3;
  
36
  
  
37
typedef struct {
  
38
	float x, y, z, w;
  
39
} Vec4;
  
40
  
30
  
41
// A packed structure representing a single vertex.
31
// A packed structure representing a single vertex.
42
// The GPU needs to know the "stride" (size) of this struct to jump between vertices.
32
// The GPU needs to know the "stride" (size) of this struct to jump between vertices.
...
79
    };
69
    };
80
}
70
}
81
  
71
  
82
#define COLOR_RED     (Vec3){ .x = 1.0f, .y = 0.0f, .z = 0.0f }
  
83
#define COLOR_GREEN   (Vec3){ .x = 0.0f, .y = 1.0f, .z = 0.0f }
  
84
#define COLOR_BLUE    (Vec3){ .x = 0.0f, .y = 0.0f, .z = 1.0f }
  
85
#define COLOR_WHITE   (Vec3){ .x = 1.0f, .y = 1.0f, .z = 1.0f }
  
86
#define COLOR_BLACK   (Vec3){ .x = 0.0f, .y = 0.0f, .z = 0.0f }
  
87
#define COLOR_YELLOW  (Vec3){ .x = 1.0f, .y = 1.0f, .z = 0.0f }
  
88
#define COLOR_CYAN    (Vec3){ .x = 0.0f, .y = 1.0f, .z = 1.0f }
  
89
#define COLOR_MAGENTA (Vec3){ .x = 1.0f, .y = 0.0f, .z = 1.0f }
  
90
#define COLOR_ORANGE  (Vec3){ .x = 1.0f, .y = 0.5f, .z = 0.0f }
  
91
#define COLOR_PURPLE  (Vec3){ .x = 0.5f, .y = 0.0f, .z = 0.5f }
  
92
  
  
93
typedef struct {
72
typedef struct {
94
	int target_fps;
73
	int target_fps;
95
	int width;
74
	int width;
...
104
  
83
  
105
Game game = {0};
84
Game game = {0};
106
  
85
  
107
unsigned char* load_ppm(const char* filename, int* width, int* height) {
  
108
	FILE* fp = fopen(filename, "rb");
  
109
	if (!fp) {
  
110
		fprintf(stderr, "Unable to open file '%s'\n", filename);
  
111
		return NULL;
  
112
	}
  
113
  
  
114
	char header[3];
  
115
	if (!fgets(header, sizeof(header), fp) || header[0] != 'P' || header[1] != '6') {
  
116
		fprintf(stderr, "Invalid PPM file (must be P6)\n");
  
117
		fclose(fp);
  
118
		return NULL;
  
119
	}
  
120
  
  
121
	// Skip comments
  
122
	int c = getc(fp);
  
123
	while (c == '#') {
  
124
		while (getc(fp) != '\n');
  
125
		c = getc(fp);
  
126
	}
  
127
	ungetc(c, fp);
  
128
  
  
129
	if (fscanf(fp, "%d %d", width, height) != 2) {
  
130
		fprintf(stderr, "Invalid PPM dimensions\n");
  
131
		fclose(fp);
  
132
		return NULL;
  
133
	}
  
134
  
  
135
	int max_val;
  
136
	if (fscanf(fp, "%d", &max_val) != 1 || max_val != 255) {
  
137
		fprintf(stderr, "Invalid PPM max value\n");
  
138
		fclose(fp);
  
139
		return NULL;
  
140
	}
  
141
  
  
142
	while (getc(fp) != '\n'); // skip single whitespace
  
143
  
  
144
	unsigned char* data = ALLOC(unsigned char, (*width) * (*height) * 3);
  
145
	if (!data) {
  
146
		fprintf(stderr, "Memory allocation failed\n");
  
147
		fclose(fp);
  
148
		return NULL;
  
149
	}
  
150
  
  
151
	if (fread(data, 1, (*width) * (*height) * 3, fp) != (size_t)((*width) * (*height) * 3)) {
  
152
		fprintf(stderr, "Error reading PPM data\n");
  
153
		FREE(data);
  
154
		fclose(fp);
  
155
		return NULL;
  
156
	}
  
157
  
  
158
	fclose(fp);
  
159
	return data;
  
160
}
  
161
  
  
162
void init_texture(void) {
86
void init_texture(void) {
163
	int width, height;
87
	Image img = read_ppm_image("textures/test.ppm");
164
	unsigned char* data = load_ppm("textures/test.ppm", &width, &height);
88
	if (!img.pixels) return;
165
	if (!data) return;
  
166
  
89
  
167
	glGenTextures(1, &game.texture_id);
90
	glGenTextures(1, &game.texture_id);
168
	glBindTexture(GL_TEXTURE_2D, game.texture_id);
91
	glBindTexture(GL_TEXTURE_2D, game.texture_id);
...
170
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
93
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
171
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
94
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
172
  
95
  
173
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
96
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, img.width, img.height, 0, GL_RGB, GL_UNSIGNED_BYTE, img.pixels);
174
  
97
  
175
	FREE(data);
98
	FREE(img.pixels);
176
}
99
}
177
  
100
  
178
void draw_text(float x, float y, void* font, const char* string, Vec3 color) {
101
void draw_text(float x, float y, void* font, const char* string, Vec3 color) {
...
diff --git a/ppm.c b/ppm.c
  
1
#include "all.h"
  
2
  
  
3
#include <stdio.h>
  
4
#include <stdlib.h>
  
5
#include <ctype.h>
  
6
#include <string.h>
  
7
  
  
8
Image read_ppm_image(const char *filename) {
  
9
	/* const char *filename = "textures/test.ppm"; */
  
10
  
  
11
	Image img = {0};
  
12
  
  
13
	FILE *f = fopen(filename, "rb");
  
14
	if (!f) {
  
15
		perror("fopen");
  
16
		return (Image){};
  
17
	}
  
18
  
  
19
  
  
20
	int j = 0;
  
21
	int in_token = 0;
  
22
	int header_done = 0;
  
23
  
  
24
	size_t pixel_count = 0;
  
25
	size_t pixel_capacity = 0;
  
26
  
  
27
	unsigned char buf[4096];
  
28
	size_t nread;
  
29
  
  
30
	while ((nread = fread(buf, 1, sizeof buf, f)) > 0) {
  
31
		for (size_t i = 0; i < nread; ++i) {
  
32
			unsigned char c = buf[i];
  
33
  
  
34
			if (!header_done) {
  
35
				if (isspace(c)) {
  
36
					if (in_token) {
  
37
						j++;
  
38
						in_token = 0;
  
39
  
  
40
						if (j == 4) {
  
41
							header_done = 1;
  
42
  
  
43
							pixel_capacity = img.width * img.height * 3;
  
44
							img.pixels = malloc(pixel_capacity);
  
45
  
  
46
							if (!img.pixels) {
  
47
								perror("malloc");
  
48
								fclose(f);
  
49
								return (Image){};
  
50
							}
  
51
						}
  
52
					}
  
53
  
  
54
					continue;
  
55
				}
  
56
  
  
57
				in_token = 1;
  
58
  
  
59
				switch (j) {
  
60
					case 0:
  
61
						{
  
62
							size_t len = strlen(img.format);
  
63
							if (len + 1 < sizeof img.format) {
  
64
								img.format[len] = (char)c;
  
65
								img.format[len + 1] = '\0';
  
66
							}
  
67
						} break;
  
68
  
  
69
					case 1:
  
70
						{
  
71
							if (isdigit(c))
  
72
								img.width = img.width * 10 + (c - '0');
  
73
						} break;
  
74
  
  
75
					case 2:
  
76
						{
  
77
							if (isdigit(c))
  
78
								img.height = img.height * 10 + (c - '0');
  
79
						} break;
  
80
  
  
81
					case 3:
  
82
						{
  
83
							if (isdigit(c))
  
84
								img.color_space = img.color_space * 10 + (c - '0');
  
85
						} break;
  
86
				}
  
87
			} else {
  
88
				if (pixel_count < pixel_capacity) {
  
89
					img.pixels[pixel_count++] = c;
  
90
				}
  
91
			}
  
92
		}
  
93
	}
  
94
  
  
95
	fclose(f);
  
96
  
  
97
	printf("format: %s\n", img.format);
  
98
	printf("width: %d\n", img.width);
  
99
	printf("height: %d\n", img.height);
  
100
	printf("color_space: %d\n", img.color_space);
  
101
	printf("pixel_count: %zu\n", pixel_count);
  
102
	printf("expected: %zu\n", pixel_capacity);
  
103
  
  
104
	return img;
  
105
}