summaryrefslogtreecommitdiff
path: root/runtime.c
diff options
context:
space:
mode:
Diffstat (limited to 'runtime.c')
-rw-r--r--runtime.c417
1 files changed, 348 insertions, 69 deletions
diff --git a/runtime.c b/runtime.c
index 32c9766..21499ce 100644
--- a/runtime.c
+++ b/runtime.c
@@ -655,16 +655,11 @@ typedef struct {
i64 clipboard_text_len;
c8 *clipboard_text;
- i64 clipboard_image_width;
- i64 clipboard_image_height;
- i64 clipboard_image_len;
- vec4_f32 *clipboard_image;
+ i64 clipboard_image_len;
+ u8 *clipboard_image;
- i64 clipboard_sound_num_channels;
- i64 clipboard_sound_sample_rate;
- i64 clipboard_sound_num_samples;
- i64 clipboard_sound_len;
- f32 *clipboard_sound;
+ i64 clipboard_sound_len;
+ u8 *clipboard_sound;
b8 key_down [MAX_NUM_KEYS];
b8 key_pressed [MAX_NUM_KEYS];
@@ -719,10 +714,10 @@ i64 network_recv(u16 slot, Network_Address address, i64 len, u8 *data, u16 *loca
i64 network_send(u16 slot, Network_Address address, i64 len, u8 *data, u16 *local_port);
// Data formats
-b8 bmp_read_from_memory(i64 *width, i64 *height, i64 colors_len, vec4_f32 *colors, i64 src_len, u8 *src);
-b8 bmp_write_to_memory (i64 *dst_len, u8 *dst, i64 width, i64 height, i64 stride, vec4_f32 *colors);
-b8 ppm_read_from_memory(i64 *width, i64 *height, i64 colors_len, vec4_f32 *colors, i64 src_len, u8 *src);
-b8 ppm_write_to_memory (i64 *dst_len, u8 *dst, i64 width, i64 height, i64 stride, vec4_f32 *colors);
+b8 bmp_read_from_memory(i64 *width, i64 *height, i64 pixels_len, vec4_f32 *pixels, i64 src_len, u8 *src);
+b8 bmp_write_to_memory (i64 *dst_len, u8 *dst, i64 width, i64 height, i64 stride, vec4_f32 *pixels);
+b8 ppm_read_from_memory(i64 *width, i64 *height, i64 pixels_len, vec4_f32 *pixels, i64 src_len, u8 *src);
+b8 ppm_write_to_memory (i64 *dst_len, u8 *dst, i64 width, i64 height, i64 stride, vec4_f32 *pixels);
b8 wav_read_from_memory(i64 *sample_rate_hz, i64 *num_channels, i64 *num_samples, i64 frames_len, f32 *frames, i64 src_len, u8 *src);
b8 wav_write_to_memory (i64 *dst_len, u8 *dst, i64 sample_rate_hz, i64 num_channels, i64 num_samples, f32 *frames);
@@ -821,6 +816,14 @@ static b8 mem_eq_(i64 len, void *x, void *y) {
return 1;
}
+static i32 abs_i32_(i32 x) {
+ return x < 0 ? -x : x;
+}
+
+static u32 min2_u32_(u32 x, u32 y) {
+ return x < y ? x : y;
+}
+
static f64 min2_(f64 x, f64 y) {
return x < y ? x : y;
}
@@ -2347,41 +2350,305 @@ static c16 *buffer_utf16_from_utf8_(c8 *s) {
//
// ================================================================
-b8 bmp_read_from_memory(i64 *width, i64 *height, i64 colors_len, vec4_f32 *colors, i64 src_len, u8 *src) {
- // TODO
+#pragma pack (push, 1)
- (void) width;
- (void) height;
- (void) colors_len;
- (void) colors;
- (void) src_len;
- (void) src;
+typedef struct {
+ u16 signature;
+ u32 file_size;
+ u32 _reserved;
+ u32 file_offset_to_pixel_array;
+} BMP_File_Header_;
- LOG_error("Not implemented.");
- return 0;
+typedef struct {
+ u32 header_size;
+ i32 image_width;
+ i32 image_height;
+ u16 planes;
+ u16 bits_per_pixel;
+ u32 compression;
+ u32 image_size;
+ u32 x_pixels_per_meter;
+ u32 y_pixels_per_meter;
+ u32 colors_in_color_table;
+ u32 important_color_count;
+ u32 red_channel_bitmask;
+ u32 green_channel_bitmask;
+ u32 blue_channel_bitmask;
+ u32 alpha_channel_bitmask;
+} BMP_Info_Header_;
+
+#define BMP_Signature_ ('B' | ('M' << 8))
+
+enum {
+ BMP_Compression_RGB_ = 0,
+};
+
+#pragma pack (pop)
+
+u32 zero_bits_u32_(u32 x) {
+ if (x == 0) return 32;
+ u32 n = 0;
+ while ((x & 1) == 0) {
+ ++n;
+ x >>= 1;
+ }
+ return n;
}
-b8 bmp_write_to_memory(i64 *dst_len, u8 *dst, i64 width, i64 height, i64 stride, vec4_f32 *colors) {
- // TODO
+b8 bmp_read_from_memory(i64 *width, i64 *height, i64 pixels_len, vec4_f32 *pixels, i64 src_len, u8 *src) {
+ if ((width == NULL) != (height == NULL)) {
+ LOG_error("Sanity");
+ return 0;
+ }
- (void) dst_len;
- (void) dst;
- (void) width;
- (void) height;
- (void) stride;
- (void) colors;
+ if (src_len <= 0 || src == NULL) {
+ LOG_error("Sanity");
+ return 0;
+ }
- LOG_error("Not implemented.");
- return 0;
+ if (src_len < (i64) sizeof(BMP_File_Header_) + (i64) sizeof(u32)) {
+ LOG_error("File too small.");
+ return 0;
+ }
+
+ BMP_File_Header_ *file_header = (BMP_File_Header_ *) src;
+ BMP_Info_Header_ *info = (BMP_Info_Header_ *) (src + sizeof(BMP_File_Header_));
+
+ if (file_header->signature != BMP_Signature_) {
+ LOG_error("Invalid signature.");
+ return 0;
+ }
+
+ if (file_header->file_size > src_len) {
+ LOG_error("Invalid image header. Actual file not big enough for specified file size.");
+ return 0;
+ }
+
+ if (file_header->file_offset_to_pixel_array >= file_header->file_size) {
+ LOG_error("Invalid image header. File not big enough for specified pixel array offset.");
+ return 0;
+ }
+
+ if (info->header_size == 12) {
+ LOG_error("Header type not supported.");
+ return 0;
+ }
+
+ if (info->header_size < 20) {
+ LOG_error("Invalid image header. Header size not big enough.");
+ return 0;
+ }
+
+ if (info->planes != 1) {
+ LOG_error("Multiple planes not supported.");
+ return 0;
+ }
+
+ if (info->compression != BMP_Compression_RGB_) {
+ LOG_error("Compression not supported.");
+ return 0;
+ }
+
+ if (info->bits_per_pixel != 24 && info->bits_per_pixel != 32) {
+ LOG_error("Unsupported bits per pixel value.");
+ return 0;
+ }
+
+ i64 image_width = abs_i32_(info->image_width);
+ i64 image_height = abs_i32_(info->image_height);
+
+ // Validate image dimensions.
+ {
+ i64 image_pixels_len = file_header->file_size - file_header->file_offset_to_pixel_array;
+
+ i64 max_line_len = image_pixels_len / image_height;
+ i64 max_pixels_per_line = (max_line_len * 8) / info->bits_per_pixel;
+ i64 max_num_pixels = max_pixels_per_line * image_height;
+
+ if (image_width * image_height > max_num_pixels) {
+ LOG_error("Invalid image header. File not bit enough for required image width and height.");
+ return 0;
+ }
+ }
+
+ if (width == NULL || height == NULL)
+ // Only validate headers.
+ return 1;
+
+ *width = image_width;
+ *height = image_height;
+
+ if (pixels_len == 0 && pixels == NULL)
+ // Only validate headers and return width and height.
+ return 1;
+
+ if (image_width * image_height > pixels_len) {
+ LOG_error("Not enough space for pixels.");
+ return 0;
+ }
+
+ u32 mask_r = 0x00ff0000;
+ u32 mask_g = 0x0000ff00;
+ u32 mask_b = 0x000000ff;
+ u32 mask_a = 0xff000000;
+
+ u32 shift_r = 16;
+ u32 shift_g = 8;
+ u32 shift_b = 0;
+ u32 shift_a = 24;
+
+ if (info->header_size >= sizeof(BMP_Info_Header_)) {
+ mask_r = info->red_channel_bitmask;
+ mask_g = info->green_channel_bitmask;
+ mask_b = info->blue_channel_bitmask;
+ mask_a = info->alpha_channel_bitmask;
+
+ shift_r = zero_bits_u32_(mask_r);
+ shift_g = zero_bits_u32_(mask_g);
+ shift_b = zero_bits_u32_(mask_b);
+ shift_a = zero_bits_u32_(mask_a);
+ }
+
+ u8 *src_pixels = src + file_header->file_offset_to_pixel_array;
+
+ i64 bytes_per_pixel = info->bits_per_pixel / 8;
+ i64 line_size = 4 * ((image_width * bytes_per_pixel + 3) / 4);
+
+ for (i64 j = 0; j < image_height; ++j) {
+ u8 *line = src_pixels + j * line_size;
+
+ for (i64 i = 0; i < image_width; ++i) {
+ u8 *p = line + i * bytes_per_pixel;
+
+ u32 pixel = p[0] | (p[1] << 8) | (p[2] << 16);
+
+ if (bytes_per_pixel == 4)
+ pixel |= p[3] << 24;
+
+ u32 r = 0;
+ u32 g = 0;
+ u32 b = 0;
+ u32 a = 255;
+
+ r = (pixel & mask_r) >> shift_r;
+ g = (pixel & mask_g) >> shift_g;
+ b = (pixel & mask_b) >> shift_b;
+
+ if (bytes_per_pixel == 4)
+ a = (pixel & mask_a) >> shift_a;
+
+ pixels[j * image_width + i] = (vec4_f32) {
+ .x = ((f32) r) / 255.0f,
+ .y = ((f32) g) / 255.0f,
+ .z = ((f32) b) / 255.0f,
+ .w = ((f32) a) / 255.0f,
+ };
+ }
+ }
+
+ return 1;
+}
+
+b8 bmp_write_to_memory(i64 *dst_len, u8 *dst, i64 width, i64 height, i64 stride, vec4_f32 *pixels) {
+ if (dst_len == NULL || pixels == NULL) {
+ LOG_error("Sanity");
+ return 0;
+ }
+
+ if (width <= 0 || height <= 0 || stride == 0) {
+ LOG_error("Sanity");
+ return 0;
+ }
+
+ if (width > 0x7fffffffll || height > 0x7fffffffll) {
+ LOG_error("Width and height too large.");
+ return 0;
+ }
+
+ b8 has_alpha_channel = 0;
+
+ for (i64 j = 0; j < height; ++j)
+ for (i64 i = 0; i < width; ++i)
+ if ((u32) (pixels[j * stride + i].w * 255.0 + 0.5) != 255) {
+ has_alpha_channel = 1;
+ goto _end_loops;
+ }
+ _end_loops:;
+
+ i64 bytes_per_pixel = has_alpha_channel ? 4 : 3;
+ i64 line_size = 4 * ((width * bytes_per_pixel + 3) / 4);
+ i64 image_size = line_size * height;
+ i64 headers_size = sizeof(BMP_File_Header_) + sizeof(BMP_Info_Header_);
+ i64 file_size = headers_size + image_size;
+
+ *dst_len = file_size;
+
+ if (dst == NULL)
+ // Only calculate required file size.
+ return 1;
+
+ BMP_File_Header_ file_header = {
+ .signature = BMP_Signature_,
+ .file_size = file_size,
+ .file_offset_to_pixel_array = headers_size,
+ };
+
+ BMP_Info_Header_ info = {
+ .header_size = sizeof(BMP_Info_Header_),
+ .image_width = width,
+ .image_height = height,
+ .planes = 1,
+ .bits_per_pixel = bytes_per_pixel * 8,
+ .compression = BMP_Compression_RGB_,
+ .image_size = image_size,
+ .red_channel_bitmask = 0x00ff0000,
+ .green_channel_bitmask = 0x0000ff00,
+ .blue_channel_bitmask = 0x000000ff,
+ .alpha_channel_bitmask = 0xff000000,
+ };
+
+ if (!native_little_endian_()) {
+ if (has_alpha_channel) {
+ info.red_channel_bitmask = 0x0000ff00;
+ info.green_channel_bitmask = 0x00ff0000;
+ info.blue_channel_bitmask = 0xff000000;
+ info.alpha_channel_bitmask = 0x000000ff;
+ } else {
+ info.red_channel_bitmask = 0x000000ff;
+ info.green_channel_bitmask = 0x0000ff00;
+ info.blue_channel_bitmask = 0x00ff0000;
+ }
+ }
+
+ if (!has_alpha_channel)
+ info.alpha_channel_bitmask = 0;
+
+ mem_cpy_(dst, &file_header, sizeof file_header);
+ mem_cpy_(dst + sizeof file_header, &info, sizeof info);
+
+ for (i64 j = 0; j < height; ++j)
+ for (i64 i = 0; i < width; ++i) {
+ vec4_f32 *s = pixels + j * stride + i;
+ u8 *d = dst + headers_size + j * line_size + i * bytes_per_pixel;
+
+ d[0] = (u8) min2_u32_((u32) (s->z * 255.0 + 0.5), 255);
+ d[1] = (u8) min2_u32_((u32) (s->y * 255.0 + 0.5), 255);
+ d[2] = (u8) min2_u32_((u32) (s->x * 255.0 + 0.5), 255);
+
+ if (has_alpha_channel)
+ d[3] = (u8) min2_u32_((u32) (s->w * 255.0 + 0.5), 255);
+ }
+
+ return 1;
}
-b8 ppm_read_from_memory(i64 *width, i64 *height, i64 colors_len, vec4_f32 *colors, i64 src_len, u8 *src) {
+b8 ppm_read_from_memory(i64 *width, i64 *height, i64 pixels_len, vec4_f32 *pixels, i64 src_len, u8 *src) {
// TODO
(void) width;
(void) height;
- (void) colors_len;
- (void) colors;
+ (void) pixels_len;
+ (void) pixels;
(void) src_len;
(void) src;
@@ -2389,7 +2656,7 @@ b8 ppm_read_from_memory(i64 *width, i64 *height, i64 colors_len, vec4_f32 *color
return 0;
}
-b8 ppm_write_to_memory(i64 *dst_len, u8 *dst, i64 width, i64 height, i64 stride, vec4_f32 *colors) {
+b8 ppm_write_to_memory(i64 *dst_len, u8 *dst, i64 width, i64 height, i64 stride, vec4_f32 *pixels) {
// TODO
(void) dst_len;
@@ -2397,7 +2664,7 @@ b8 ppm_write_to_memory(i64 *dst_len, u8 *dst, i64 width, i64 height, i64 stride,
(void) width;
(void) height;
(void) stride;
- (void) colors;
+ (void) pixels;
LOG_error("Not implemented.");
return 0;
@@ -4109,8 +4376,8 @@ i32 handle_main_window_events(void) {
if (ev.xselectionrequest.property != None) {
if (ev.xselectionrequest.target == _targets) {
b8 has_text = g_platform.clipboard_text_len > 0;
- b8 has_image = g_platform.clipboard_image_width > 0 && g_platform.clipboard_image_height > 0;
- b8 has_sound = g_platform.clipboard_sound_num_samples > 0;
+ b8 has_image = g_platform.clipboard_image_len > 0;
+ b8 has_sound = g_platform.clipboard_sound_len > 0;
if (!has_text && !has_image && !has_sound)
XDeleteProperty(
@@ -5439,9 +5706,10 @@ TEST("BMP image") {
for (i64 j = 0; j < height; ++j)
for (i64 i = 0; i < width; ++i)
pixels[j * stride + i] = (vec4_f32) {
- (f64) i / (f64) (width - 1),
- (f64) j / (f64) (height - 1),
- (f64) (i + j) / (f64) (width + height - 2)
+ (f32) i / (f64) (width - 1),
+ (f32) j / (f64) (height - 1),
+ (f32) (i + j) / (f64) (width + height - 2),
+ 1.0f,
};
i64 num_bytes = 0;
@@ -5451,14 +5719,14 @@ TEST("BMP image") {
REQUIRE(bmp_write_to_memory(&num_bytes, NULL, width, height, stride, pixels));
REQUIRE(num_bytes != 0);
- mem_set_(pixels, 0, pixels_len * sizeof *pixels);
-
resize_dynamic_array_exact(&bytes_len, (void **) &bytes, sizeof *bytes, num_bytes);
REQUIRE_EQ(bytes_len, num_bytes);
REQUIRE(bmp_write_to_memory(&num_bytes, bytes, width, height, stride, pixels));
REQUIRE_EQ(num_bytes, bytes_len);
+ mem_set_(pixels, 0, pixels_len * sizeof *pixels);
+
i64 read_width = 0;
i64 read_height = 0;
@@ -5469,16 +5737,21 @@ TEST("BMP image") {
b8 ok = 1;
for (i64 j = 0; j < height; ++j)
- for (i64 i = 0; i < width; ++i)
- if (pixels[j * stride + i].x < (f64) i / (f64) (width - 1) - 1e-7
- || pixels[j * stride + i].y < (f64) j / (f64) (height - 1) - 1e-7
- || pixels[j * stride + i].z < (f64) (i + j) / (f64) (width + height - 2) - 1e-7
- || pixels[j * stride + i].x > (f64) i / (f64) (width - 1) + 1e-7
- || pixels[j * stride + i].y > (f64) j / (f64) (height - 1) + 1e-7
- || pixels[j * stride + i].z > (f64) (i + j) / (f64) (width + height - 2) + 1e-7) {
+ for (i64 i = 0; i < width; ++i) {
+ vec4_f32 s = pixels[j * stride + i];
+ vec4_f32 d = {
+ (f32) i / (f32) (width - 1),
+ (f32) j / (f32) (height - 1),
+ (f32) (i + j) / (f32) (width + height - 2)
+ };
+
+ if (s.x < d.x - 3e-3 || s.y < d.y - 3e-3 || s.z < d.z - 3e-3
+ || s.x > d.x + 3e-3 || s.y > d.y + 3e-3 || s.z > d.z + 3e-3) {
ok = 0;
- break;
+ goto _end_loops;
}
+ }
+ _end_loops:;
REQUIRE(ok);
@@ -5499,9 +5772,9 @@ TEST("PPM image") {
for (i64 j = 0; j < height; ++j)
for (i64 i = 0; i < width; ++i)
pixels[j * stride + i] = (vec4_f32) {
- (f64) i / (f64) (width - 1),
- (f64) j / (f64) (height - 1),
- (f64) (i + j) / (f64) (width + height - 2)
+ (f32) i / (f64) (width - 1),
+ (f32) j / (f64) (height - 1),
+ (f32) (i + j) / (f64) (width + height - 2)
};
i64 num_bytes = 0;
@@ -5511,14 +5784,14 @@ TEST("PPM image") {
REQUIRE(ppm_write_to_memory(&num_bytes, NULL, width, height, stride, pixels));
REQUIRE(num_bytes != 0);
- mem_set_(pixels, 0, pixels_len * sizeof *pixels);
-
resize_dynamic_array_exact(&bytes_len, (void **) &bytes, sizeof *bytes, num_bytes);
REQUIRE_EQ(bytes_len, num_bytes);
REQUIRE(ppm_write_to_memory(&num_bytes, bytes, width, height, stride, pixels));
REQUIRE_EQ(num_bytes, bytes_len);
+ mem_set_(pixels, 0, pixels_len * sizeof *pixels);
+
i64 read_width = 0;
i64 read_height = 0;
@@ -5529,16 +5802,21 @@ TEST("PPM image") {
b8 ok = 1;
for (i64 j = 0; j < height; ++j)
- for (i64 i = 0; i < width; ++i)
- if (pixels[j * stride + i].x < (f64) i / (f64) (width - 1) - 1e-7
- || pixels[j * stride + i].y < (f64) j / (f64) (height - 1) - 1e-7
- || pixels[j * stride + i].z < (f64) (i + j) / (f64) (width + height - 2) - 1e-7
- || pixels[j * stride + i].x > (f64) i / (f64) (width - 1) + 1e-7
- || pixels[j * stride + i].y > (f64) j / (f64) (height - 1) + 1e-7
- || pixels[j * stride + i].z > (f64) (i + j) / (f64) (width + height - 2) + 1e-7) {
+ for (i64 i = 0; i < width; ++i) {
+ vec4_f32 s = pixels[j * stride + i];
+ vec4_f32 d = {
+ (f32) i / (f32) (width - 1),
+ (f32) j / (f32) (height - 1),
+ (f32) (i + j) / (f32) (width + height - 2)
+ };
+
+ if (s.x < d.x - 3e-3 || s.y < d.y - 3e-3 || s.z < d.z - 3e-3
+ || s.x > d.x + 3e-3 || s.y > d.y + 3e-3 || s.z > d.z + 3e-3) {
ok = 0;
- break;
+ goto _end_loops;
}
+ }
+ _end_loops:;
REQUIRE(ok);
@@ -5575,14 +5853,14 @@ TEST("WAV audio") {
REQUIRE(wav_write_to_memory(&num_bytes, NULL, sample_rate_hz, num_channels, num_samples, frames));
REQUIRE(num_bytes != 0);
- mem_set_(frames, 0, frames_len* sizeof *frames);
-
resize_dynamic_array_exact(&bytes_len, (void **) &bytes, sizeof *bytes, num_bytes);
REQUIRE_EQ(bytes_len, num_bytes);
REQUIRE(wav_write_to_memory(&num_bytes, bytes, sample_rate_hz, num_channels, num_samples, frames));
REQUIRE_EQ(num_bytes, bytes_len);
+ mem_set_(frames, 0, frames_len* sizeof *frames);
+
i64 read_sample_rate_hz;
i64 read_num_channels;
i64 read_num_samples;
@@ -5602,9 +5880,10 @@ TEST("WAV audio") {
if (frames[i * num_channels + k] < amplitude - 1e-7
|| frames[i * num_channels + k] > amplitude + 1e-7) {
ok = 0;
- break;
+ goto _end_loops;
}
}
+ _end_loops:;
REQUIRE(ok);