diff options
| author | Jeroen van Rijn <Kelimion@users.noreply.github.com> | 2024-06-06 16:32:18 +0200 |
|---|---|---|
| committer | Jeroen van Rijn <Kelimion@users.noreply.github.com> | 2024-06-06 16:32:18 +0200 |
| commit | 678fdae966e0e4b959a917f11d276b60fb78772f (patch) | |
| tree | 75162c9da40285c635ed7f99af1901273c26a7c5 /core/image | |
| parent | 3a9b86628a484aa03b594599653c1cab4b916c8e (diff) | |
Rebased.
Diffstat (limited to 'core/image')
| -rw-r--r-- | core/image/bmp/bmp.odin | 652 | ||||
| -rw-r--r-- | core/image/bmp/bmp_js.odin | 4 | ||||
| -rw-r--r-- | core/image/bmp/bmp_os.odin | 19 | ||||
| -rw-r--r-- | core/image/common.odin | 126 |
4 files changed, 801 insertions, 0 deletions
diff --git a/core/image/bmp/bmp.odin b/core/image/bmp/bmp.odin new file mode 100644 index 000000000..d9ecb55e5 --- /dev/null +++ b/core/image/bmp/bmp.odin @@ -0,0 +1,652 @@ +// package bmp implements a Microsoft BMP image reader +package core_image_bmp + +import "core:image" +import "core:bytes" +import "core:compress" +import "core:mem" +import "base:intrinsics" +import "base:runtime" +@(require) import "core:fmt" + +Error :: image.Error +Image :: image.Image +Options :: image.Options + +RGB_Pixel :: image.RGB_Pixel +RGBA_Pixel :: image.RGBA_Pixel + +FILE_HEADER_SIZE :: 14 +INFO_STUB_SIZE :: FILE_HEADER_SIZE + size_of(image.BMP_Version) + +load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { + ctx := &compress.Context_Memory_Input{ + input_data = data, + } + + img, err = load_from_context(ctx, options, allocator) + return img, err +} + +@(optimization_mode="speed") +load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { + context.allocator = allocator + options := options + + // For compress.read_slice(), until that's rewritten to not use temp allocator + runtime.DEFAULT_TEMP_ALLOCATOR_TEMP_GUARD() + + if .info in options { + options |= {.return_metadata, .do_not_decompress_image} + options -= {.info} + } + + if .return_header in options && .return_metadata in options { + options -= {.return_header} + } + + info_buf: [size_of(image.BMP_Header)]u8 + + // Read file header (14) + info size (4) + stub_data := compress.read_slice(ctx, INFO_STUB_SIZE) or_return + copy(info_buf[:], stub_data[:]) + stub_info := transmute(image.BMP_Header)info_buf + + if stub_info.magic != .Bitmap { + for v in image.BMP_Magic { + if stub_info.magic == v { + return img, .Unsupported_OS2_File + } + } + return img, .Invalid_Signature + } + + info: image.BMP_Header + switch stub_info.info_size { + case .OS2_v1: + // Read the remainder of the header + os2_data := compress.read_data(ctx, image.OS2_Header) or_return + + info = transmute(image.BMP_Header)info_buf + info.width = i32le(os2_data.width) + info.height = i32le(os2_data.height) + info.planes = os2_data.planes + info.bpp = os2_data.bpp + + switch info.bpp { + case 1, 4, 8, 24: + case: + return img, .Unsupported_BPP + } + + case .ABBR_16 ..= .V5: + // Sizes include V3, V4, V5 and OS2v2 outright, but can also handle truncated headers. + // Sometimes called BITMAPV2INFOHEADER or BITMAPV3INFOHEADER. + // Let's just try to process it. + + to_read := int(stub_info.info_size) - size_of(image.BMP_Version) + info_data := compress.read_slice(ctx, to_read) or_return + copy(info_buf[INFO_STUB_SIZE:], info_data[:]) + + // Update info struct with the rest of the data we read + info = transmute(image.BMP_Header)info_buf + + case: + return img, .Unsupported_BMP_Version + } + + /* TODO(Jeroen): Add a "strict" option to catch these non-issues that violate spec? + if info.planes != 1 { + return img, .Invalid_Planes_Value + } + */ + + if img == nil { + img = new(Image) + } + img.which = .BMP + + img.metadata = new_clone(image.BMP_Info{ + info = info, + }) + + img.width = abs(int(info.width)) + img.height = abs(int(info.height)) + img.channels = 3 + img.depth = 8 + + if img.width == 0 || img.height == 0 { + return img, .Invalid_Image_Dimensions + } + + total_pixels := abs(img.width * img.height) + if total_pixels > image.MAX_DIMENSIONS { + return img, .Image_Dimensions_Too_Large + } + + // TODO(Jeroen): Handle RGBA. + switch info.compression { + case .Bit_Fields, .Alpha_Bit_Fields: + switch info.bpp { + case 16, 32: + make_output(img, allocator) or_return + decode_rgb(ctx, img, info, allocator) or_return + case: + if is_os2(info.info_size) { + return img, .Unsupported_Compression + } + return img, .Unsupported_BPP + } + case .RGB: + make_output(img, allocator) or_return + decode_rgb(ctx, img, info, allocator) or_return + case .RLE4, .RLE8: + make_output(img, allocator) or_return + decode_rle(ctx, img, info, allocator) or_return + case .CMYK, .CMYK_RLE4, .CMYK_RLE8: fallthrough + case .PNG, .JPEG: fallthrough + case: return img, .Unsupported_Compression + } + + // Flipped vertically + if info.height < 0 { + pixels := mem.slice_data_cast([]RGB_Pixel, img.pixels.buf[:]) + for y in 0..<img.height / 2 { + for x in 0..<img.width { + top := y * img.width + x + bot := (img.height - y - 1) * img.width + x + + pixels[top], pixels[bot] = pixels[bot], pixels[top] + } + } + } + return +} + +is_os2 :: proc(version: image.BMP_Version) -> (res: bool) { + #partial switch version { + case .OS2_v1, .OS2_v2: return true + case: return false + } +} + +make_output :: proc(img: ^Image, allocator := context.allocator) -> (err: Error) { + assert(img != nil) + bytes_needed := img.channels * img.height * img.width + img.pixels.buf = make([dynamic]u8, bytes_needed, allocator) + if len(img.pixels.buf) != bytes_needed { + return .Unable_To_Allocate_Or_Resize + } + return +} + +write :: proc(img: ^Image, x, y: int, pix: RGB_Pixel) -> (err: Error) { + if y >= img.height || x >= img.width { + return .Corrupt + } + out := mem.slice_data_cast([]RGB_Pixel, img.pixels.buf[:]) + assert(img.height >= 1 && img.width >= 1) + out[(img.height - y - 1) * img.width + x] = pix + return +} + +Bitmask :: struct { + mask: [4]u32le `fmt:"b"`, + shift: [4]u32le, + bits: [4]u32le, +} + +read_or_make_bit_masks :: proc(ctx: ^$C, info: image.BMP_Header) -> (res: Bitmask, read: int, err: Error) { + ctz :: intrinsics.count_trailing_zeros + c1s :: intrinsics.count_ones + + #partial switch info.compression { + case .RGB: + switch info.bpp { + case 16: + return { + mask = {31 << 10, 31 << 5, 31, 0}, + shift = { 10, 5, 0, 0}, + bits = { 5, 5, 5, 0}, + }, int(4 * info.colors_used), nil + + case 32: + return { + mask = {255 << 16, 255 << 8, 255, 255 << 24}, + shift = { 16, 8, 0, 24}, + bits = { 8, 8, 8, 8}, + }, int(4 * info.colors_used), nil + + case: return {}, 0, .Unsupported_BPP + } + case .Bit_Fields, .Alpha_Bit_Fields: + bf := info.masks + alpha_mask := false + bit_count: u32le + + #partial switch info.info_size { + case .ABBR_52 ..= .V5: + // All possible BMP header sizes 52+ bytes long, includes V4 + V5 + // Bit fields were read as part of the header + // V3 header is 40 bytes. We need 56 at a minimum for RGBA bit fields in the next section. + if info.info_size >= .ABBR_56 { + alpha_mask = true + } + + case .V3: + // Version 3 doesn't have a bit field embedded, but can still have a 3 or 4 color bit field. + // Because it wasn't read as part of the header, we need to read it now. + + if info.compression == .Alpha_Bit_Fields { + bf = compress.read_data(ctx, [4]u32le) or_return + alpha_mask = true + read = 16 + } else { + bf.xyz = compress.read_data(ctx, [3]u32le) or_return + read = 12 + } + + case: + // Bit fields are unhandled for this BMP version + return {}, 0, .Bitfield_Version_Unhandled + } + + if alpha_mask { + res = { + mask = {bf.r, bf.g, bf.b, bf.a}, + shift = {ctz(bf.r), ctz(bf.g), ctz(bf.b), ctz(bf.a)}, + bits = {c1s(bf.r), c1s(bf.g), c1s(bf.b), c1s(bf.a)}, + } + + bit_count = res.bits.r + res.bits.g + res.bits.b + res.bits.a + } else { + res = { + mask = {bf.r, bf.g, bf.b, 0}, + shift = {ctz(bf.r), ctz(bf.g), ctz(bf.b), 0}, + bits = {c1s(bf.r), c1s(bf.g), c1s(bf.b), 0}, + } + + bit_count = res.bits.r + res.bits.g + res.bits.b + } + + if bit_count > u32le(info.bpp) { + err = .Bitfield_Sum_Exceeds_BPP + } + + overlapped := res.mask.r | res.mask.g | res.mask.b | res.mask.a + if c1s(overlapped) < bit_count { + err = .Bitfield_Overlapped + } + return res, read, err + + case: + return {}, 0, .Unsupported_Compression + } + return +} + +scale :: proc(val: $T, mask, shift, bits: u32le) -> (res: u8) { + if bits == 0 { return 0 } // Guard against malformed bit fields + v := (u32le(val) & mask) >> shift + mask_in := u32le(1 << bits) - 1 + return u8(v * 255 / mask_in) +} + +decode_rgb :: proc(ctx: ^$C, img: ^Image, info: image.BMP_Header, allocator := context.allocator) -> (err: Error) { + pixel_offset := int(info.pixel_offset) + pixel_offset -= int(info.info_size) + FILE_HEADER_SIZE + + palette: [256]RGBA_Pixel + + // Palette size is info.colors_used if populated. If not it's min(1 << bpp, offset to the pixels / channel count) + colors_used := min(256, 1 << info.bpp if info.colors_used == 0 else info.colors_used) + max_colors := pixel_offset / 3 if info.info_size == .OS2_v1 else pixel_offset / 4 + colors_used = min(colors_used, u32le(max_colors)) + + switch info.bpp { + case 1: + if info.info_size == .OS2_v1 { + // 2 x RGB palette of instead of variable RGBA palette + for i in 0..<colors_used { + palette[i].rgb = image.read_data(ctx, RGB_Pixel) or_return + } + pixel_offset -= int(3 * colors_used) + } else { + for i in 0..<colors_used { + palette[i] = image.read_data(ctx, RGBA_Pixel) or_return + } + pixel_offset -= int(4 * colors_used) + } + skip_space(ctx, pixel_offset) + + stride := (img.width + 7) / 8 + for y in 0..<img.height { + data := compress.read_slice(ctx, stride) or_return + for x in 0..<img.width { + shift := u8(7 - (x & 0x07)) + p := (data[x / 8] >> shift) & 0x01 + write(img, x, y, palette[p].bgr) or_return + } + } + + case 2: // Non-standard on modern Windows, but was allowed on WinCE + for i in 0..<colors_used { + palette[i] = image.read_data(ctx, RGBA_Pixel) or_return + } + pixel_offset -= int(4 * colors_used) + skip_space(ctx, pixel_offset) + + stride := (img.width + 3) / 4 + for y in 0..<img.height { + data := compress.read_slice(ctx, stride) or_return + for x in 0..<img.width { + shift := 6 - (x & 0x03) << 1 + p := (data[x / 4] >> u8(shift)) & 0x03 + write(img, x, y, palette[p].bgr) or_return + } + } + + case 4: + if info.info_size == .OS2_v1 { + // 16 x RGB palette of instead of variable RGBA palette + for i in 0..<colors_used { + palette[i].rgb = image.read_data(ctx, RGB_Pixel) or_return + } + pixel_offset -= int(3 * colors_used) + } else { + for i in 0..<colors_used { + palette[i] = image.read_data(ctx, RGBA_Pixel) or_return + } + pixel_offset -= int(4 * colors_used) + } + skip_space(ctx, pixel_offset) + + stride := (img.width + 1) / 2 + for y in 0..<img.height { + data := compress.read_slice(ctx, stride) or_return + for x in 0..<img.width { + p := data[x / 2] >> 4 if x & 1 == 0 else data[x / 2] + write(img, x, y, palette[p & 0x0f].bgr) or_return + } + } + + case 8: + if info.info_size == .OS2_v1 { + // 256 x RGB palette of instead of variable RGBA palette + for i in 0..<colors_used { + palette[i].rgb = image.read_data(ctx, RGB_Pixel) or_return + } + pixel_offset -= int(3 * colors_used) + } else { + for i in 0..<colors_used { + palette[i] = image.read_data(ctx, RGBA_Pixel) or_return + } + pixel_offset -= int(4 * colors_used) + } + skip_space(ctx, pixel_offset) + + stride := align4(img.width) + for y in 0..<img.height { + data := compress.read_slice(ctx, stride) or_return + for x in 0..<img.width { + write(img, x, y, palette[data[x]].bgr) or_return + } + } + + case 16: + bm, read := read_or_make_bit_masks(ctx, info) or_return + // Skip optional palette and other data + pixel_offset -= read + skip_space(ctx, pixel_offset) + + stride := align4(img.width * 2) + for y in 0..<img.height { + data := compress.read_slice(ctx, stride) or_return + pixels := mem.slice_data_cast([]u16le, data) + for x in 0..<img.width { + v := pixels[x] + r := scale(v, bm.mask.r, bm.shift.r, bm.bits.r) + g := scale(v, bm.mask.g, bm.shift.g, bm.bits.g) + b := scale(v, bm.mask.b, bm.shift.b, bm.bits.b) + write(img, x, y, RGB_Pixel{r, g, b}) or_return + } + } + + case 24: + // Eat useless palette and other padding + skip_space(ctx, pixel_offset) + + stride := align4(img.width * 3) + for y in 0..<img.height { + data := compress.read_slice(ctx, stride) or_return + pixels := mem.slice_data_cast([]RGB_Pixel, data) + for x in 0..<img.width { + write(img, x, y, pixels[x].bgr) or_return + } + } + + case 32: + bm, read := read_or_make_bit_masks(ctx, info) or_return + // Skip optional palette and other data + pixel_offset -= read + skip_space(ctx, pixel_offset) + + for y in 0..<img.height { + data := compress.read_slice(ctx, img.width * size_of(RGBA_Pixel)) or_return + pixels := mem.slice_data_cast([]u32le, data) + for x in 0..<img.width { + v := pixels[x] + r := scale(v, bm.mask.r, bm.shift.r, bm.bits.r) + g := scale(v, bm.mask.g, bm.shift.g, bm.bits.g) + b := scale(v, bm.mask.b, bm.shift.b, bm.bits.b) + write(img, x, y, RGB_Pixel{r, g, b}) or_return + } + } + + case: + return .Unsupported_BPP + } + return nil +} + +decode_rle :: proc(ctx: ^$C, img: ^Image, info: image.BMP_Header, allocator := context.allocator) -> (err: Error) { + pixel_offset := int(info.pixel_offset) + pixel_offset -= int(info.info_size) + FILE_HEADER_SIZE + + bytes_needed := size_of(RGB_Pixel) * img.height * img.width + if resize(&img.pixels.buf, bytes_needed) != nil { + return .Unable_To_Allocate_Or_Resize + } + out := mem.slice_data_cast([]RGB_Pixel, img.pixels.buf[:]) + assert(len(out) == img.height * img.width) + + palette: [256]RGBA_Pixel + + switch info.bpp { + case 4: + colors_used := info.colors_used if info.colors_used > 0 else 16 + colors_used = min(colors_used, 16) + + for i in 0..<colors_used { + palette[i] = image.read_data(ctx, RGBA_Pixel) or_return + pixel_offset -= size_of(RGBA_Pixel) + } + skip_space(ctx, pixel_offset) + + pixel_size := info.size - info.pixel_offset + remaining := compress.input_size(ctx) or_return + if remaining < i64(pixel_size) { + return .Corrupt + } + + data := make([]u8, int(pixel_size) + 4) + defer delete(data) + + for i in 0..<pixel_size { + data[i] = image.read_u8(ctx) or_return + } + + y, x := 0, 0 + index := 0 + for { + if len(data[index:]) < 2 { + return .Corrupt + } + + if data[index] > 0 { + for count in 0..<data[index] { + if count & 1 == 1 { + write(img, x, y, palette[(data[index + 1] >> 0) & 0x0f].bgr) + } else { + write(img, x, y, palette[(data[index + 1] >> 4) & 0x0f].bgr) + } + x += 1 + } + index += 2 + } else { + switch data[index + 1] { + case 0: // EOL + x = 0; y += 1 + index += 2 + case 1: // EOB + return + case 2: // MOVE + x += int(data[index + 2]) + y += int(data[index + 3]) + index += 4 + case: // Literals + run_length := int(data[index + 1]) + aligned := (align4(run_length) >> 1) + 2 + + if index + aligned >= len(data) { + return .Corrupt + } + + for count in 0..<run_length { + val := data[index + 2 + count / 2] + if count & 1 == 1 { + val &= 0xf + } else { + val = val >> 4 + } + write(img, x, y, palette[val].bgr) + x += 1 + } + index += aligned + } + } + } + + case 8: + colors_used := info.colors_used if info.colors_used > 0 else 256 + colors_used = min(colors_used, 256) + + for i in 0..<colors_used { + palette[i] = image.read_data(ctx, RGBA_Pixel) or_return + pixel_offset -= size_of(RGBA_Pixel) + } + skip_space(ctx, pixel_offset) + + pixel_size := info.size - info.pixel_offset + remaining := compress.input_size(ctx) or_return + if remaining < i64(pixel_size) { + return .Corrupt + } + + data := make([]u8, int(pixel_size) + 4) + defer delete(data) + + for i in 0..<pixel_size { + data[i] = image.read_u8(ctx) or_return + } + + y, x := 0, 0 + index := 0 + for { + if len(data[index:]) < 2 { + return .Corrupt + } + + if data[index] > 0 { + for _ in 0..<data[index] { + write(img, x, y, palette[data[index + 1]].bgr) + x += 1 + } + index += 2 + } else { + switch data[index + 1] { + case 0: // EOL + x = 0; y += 1 + index += 2 + case 1: // EOB + return + case 2: // MOVE + x += int(data[index + 2]) + y += int(data[index + 3]) + index += 4 + case: // Literals + run_length := int(data[index + 1]) + aligned := align2(run_length) + 2 + + if index + aligned >= len(data) { + return .Corrupt + } + for count in 0..<run_length { + write(img, x, y, palette[data[index + 2 + count]].bgr) + x += 1 + } + index += aligned + } + } + } + + case: + return .Unsupported_BPP + } + return nil +} + +align2 :: proc(width: int) -> (stride: int) { + stride = width + if width & 1 != 0 { + stride += 2 - (width & 1) + } + return +} + +align4 :: proc(width: int) -> (stride: int) { + stride = width + if width & 3 != 0 { + stride += 4 - (width & 3) + } + return +} + +skip_space :: proc(ctx: ^$C, bytes_to_skip: int) -> (err: Error) { + if bytes_to_skip < 0 { + return .Corrupt + } + for _ in 0..<bytes_to_skip { + image.read_u8(ctx) or_return + } + return +} + +// Cleanup of image-specific data. +destroy :: proc(img: ^Image) { + if img == nil { + // Nothing to do. Load must've returned with an error. + return + } + + bytes.buffer_destroy(&img.pixels) + if v, ok := img.metadata.(^image.BMP_Info); ok { + free(v) + } + free(img) +} + +@(init, private) +_register :: proc() { + image.register(.BMP, load_from_bytes, destroy) +}
\ No newline at end of file diff --git a/core/image/bmp/bmp_js.odin b/core/image/bmp/bmp_js.odin new file mode 100644 index 000000000..d87a7d2d5 --- /dev/null +++ b/core/image/bmp/bmp_js.odin @@ -0,0 +1,4 @@ +//+build js +package core_image_bmp + +load :: proc{load_from_bytes, load_from_context} diff --git a/core/image/bmp/bmp_os.odin b/core/image/bmp/bmp_os.odin new file mode 100644 index 000000000..2245db4fd --- /dev/null +++ b/core/image/bmp/bmp_os.odin @@ -0,0 +1,19 @@ +//+build !js +package core_image_bmp + +import "core:os" + +load :: proc{load_from_file, load_from_bytes, load_from_context} + +load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { + context.allocator = allocator + + data, ok := os.read_entire_file(filename) + defer delete(data) + + if ok { + return load_from_bytes(data, options) + } else { + return nil, .Unable_To_Read_File + } +}
\ No newline at end of file diff --git a/core/image/common.odin b/core/image/common.odin index b576a9521..fe75371b3 100644 --- a/core/image/common.odin +++ b/core/image/common.odin @@ -12,6 +12,7 @@ package image import "core:bytes" import "core:mem" +import "core:io" import "core:compress" import "base:runtime" @@ -62,6 +63,7 @@ Image_Metadata :: union #shared_nil { ^PNG_Info, ^QOI_Info, ^TGA_Info, + ^BMP_Info, } @@ -159,11 +161,13 @@ Error :: union #shared_nil { Netpbm_Error, PNG_Error, QOI_Error, + BMP_Error, compress.Error, compress.General_Error, compress.Deflate_Error, compress.ZLIB_Error, + io.Error, runtime.Allocator_Error, } @@ -197,6 +201,128 @@ General_Image_Error :: enum { } /* + BMP-specific +*/ +BMP_Error :: enum { + None = 0, + Invalid_File_Size, + Unsupported_BMP_Version, + Unsupported_OS2_File, + Unsupported_Compression, + Unsupported_BPP, + Invalid_Stride, + Invalid_Color_Count, + Implausible_File_Size, + Bitfield_Version_Unhandled, // We don't (yet) handle bit fields for this BMP version. + Bitfield_Sum_Exceeds_BPP, // Total mask bit count > bpp + Bitfield_Overlapped, // Channel masks overlap +} + +// img.metadata is wrapped in a struct in case we need to add to it later +// without putting it in BMP_Header +BMP_Info :: struct { + info: BMP_Header, +} + +BMP_Magic :: enum u16le { + Bitmap = 0x4d42, // 'BM' + OS2_Bitmap_Array = 0x4142, // 'BA' + OS2_Icon = 0x4349, // 'IC', + OS2_Color_Icon = 0x4943, // 'CI' + OS2_Pointer = 0x5450, // 'PT' + OS2_Color_Pointer = 0x5043, // 'CP' +} + +// See: http://justsolve.archiveteam.org/wiki/BMP#Well-known_versions +BMP_Version :: enum u32le { + OS2_v1 = 12, // BITMAPCOREHEADER (Windows V2 / OS/2 version 1.0) + OS2_v2 = 64, // BITMAPCOREHEADER2 (OS/2 version 2.x) + V3 = 40, // BITMAPINFOHEADER + V4 = 108, // BITMAPV4HEADER + V5 = 124, // BITMAPV5HEADER + + ABBR_16 = 16, // Abbreviated + ABBR_24 = 24, // .. + ABBR_48 = 48, // .. + ABBR_52 = 52, // .. + ABBR_56 = 56, // .. +} + +BMP_Header :: struct #packed { + // File header + magic: BMP_Magic, + size: u32le, + _res1: u16le, // Reserved; must be zero + _res2: u16le, // Reserved; must be zero + pixel_offset: u32le, // Offset in bytes, from the beginning of BMP_Header to the pixel data + // V3 + info_size: BMP_Version, + width: i32le, + height: i32le, + planes: u16le, + bpp: u16le, + compression: BMP_Compression, + image_size: u32le, + pels_per_meter: [2]u32le, + colors_used: u32le, + colors_important: u32le, // OS2_v2 is equal up to here + // V4 + masks: [4]u32le `fmt:"32b"`, + colorspace: BMP_Logical_Color_Space, + endpoints: BMP_CIEXYZTRIPLE, + gamma: [3]BMP_GAMMA16_16, + // V5 + intent: BMP_Gamut_Mapping_Intent, + profile_data: u32le, + profile_size: u32le, + reserved: u32le, +} +#assert(size_of(BMP_Header) == 138) + +OS2_Header :: struct #packed { + // BITMAPCOREHEADER minus info_size field + width: i16le, + height: i16le, + planes: u16le, + bpp: u16le, +} +#assert(size_of(OS2_Header) == 8) + +BMP_Compression :: enum u32le { + RGB = 0x0000, + RLE8 = 0x0001, + RLE4 = 0x0002, + Bit_Fields = 0x0003, // If Windows + Huffman1D = 0x0003, // If OS2v2 + JPEG = 0x0004, // If Windows + RLE24 = 0x0004, // If OS2v2 + PNG = 0x0005, + Alpha_Bit_Fields = 0x0006, + CMYK = 0x000B, + CMYK_RLE8 = 0x000C, + CMYK_RLE4 = 0x000D, +} + +BMP_Logical_Color_Space :: enum u32le { + CALIBRATED_RGB = 0x00000000, + sRGB = 0x73524742, // 'sRGB' + WINDOWS_COLOR_SPACE = 0x57696E20, // 'Win ' +} + +BMP_FXPT2DOT30 :: u32le +BMP_CIEXYZ :: [3]BMP_FXPT2DOT30 +BMP_CIEXYZTRIPLE :: [3]BMP_CIEXYZ +BMP_GAMMA16_16 :: [2]u16le + +BMP_Gamut_Mapping_Intent :: enum u32le { + INVALID = 0x00000000, // If not V5, this field will just be zero-initialized and not valid. + ABS_COLORIMETRIC = 0x00000008, + BUSINESS = 0x00000001, + GRAPHICS = 0x00000002, + IMAGES = 0x00000004, +} + +/* Netpbm-specific definitions */ Netpbm_Format :: enum { |