diff options
author | Mitya Selivanov <automainint@guattari.tech> | 2025-04-27 15:37:11 +0200 |
---|---|---|
committer | Mitya Selivanov <automainint@guattari.tech> | 2025-04-27 15:37:11 +0200 |
commit | 33c3a3095579e8d2dec283ad6028a717600e1d0a (patch) | |
tree | 4a6f134ef604d5dc8f87d25a18b5da9c820c7d34 /runtime.c | |
parent | d033c731fffac4e3a83e324779ab06ecded38977 (diff) | |
download | reduced_system_layer-33c3a3095579e8d2dec283ad6028a717600e1d0a.zip |
BMP format
Diffstat (limited to 'runtime.c')
-rw-r--r-- | runtime.c | 417 |
1 files changed, 348 insertions, 69 deletions
@@ -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); |