diff options
| author | Benoit Jacquier <benoit.jacquier@gmail.com> | 2022-08-27 16:07:21 +0200 |
|---|---|---|
| committer | Benoit Jacquier <benoit.jacquier@gmail.com> | 2022-08-27 16:07:21 +0200 |
| commit | 00f2e911a73e99b1283306272ff433984d90486c (patch) | |
| tree | b988e610ff5127dcf15999a2416b59164927b3b8 /core | |
| parent | ecd81e8a5368321f28babd8d18e53401451b8667 (diff) | |
Add support for basic TGA loading
Diffstat (limited to 'core')
| -rw-r--r-- | core/image/common.odin | 6 | ||||
| -rw-r--r-- | core/image/tga/tga.odin | 146 |
2 files changed, 151 insertions, 1 deletions
diff --git a/core/image/common.odin b/core/image/common.odin index baacd64d9..beb3f93ee 100644 --- a/core/image/common.odin +++ b/core/image/common.odin @@ -61,6 +61,7 @@ Image_Metadata :: union #shared_nil { ^Netpbm_Info, ^PNG_Info, ^QOI_Info, + ^TGA_Info, } @@ -168,6 +169,7 @@ Error :: union #shared_nil { General_Image_Error :: enum { None = 0, + Unsupported_Option, // File I/O Unable_To_Read_File, Unable_To_Write_File, @@ -390,6 +392,10 @@ TGA_Header :: struct #packed { } #assert(size_of(TGA_Header) == 18) +TGA_Info :: struct { + header: TGA_Header, +} + // Function to help with image buffer calculations compute_buffer_size :: proc(width, height, channels, depth: int, extra_row_bytes := int(0)) -> (size: int) { size = ((((channels * width * depth) + 7) >> 3) + extra_row_bytes) * height diff --git a/core/image/tga/tga.odin b/core/image/tga/tga.odin index 67a088eb5..9b56b9db4 100644 --- a/core/image/tga/tga.odin +++ b/core/image/tga/tga.odin @@ -14,6 +14,11 @@ import "core:mem" import "core:image" import "core:bytes" import "core:os" +import "core:compress" + +// TODO: alpha_premultiply support +// TODO: RLE decompression + Error :: image.Error Image :: image.Image @@ -98,4 +103,143 @@ save_to_file :: proc(output: string, img: ^Image, options := Options{}, allocato return nil if write_ok else .Unable_To_Write_File } -save :: proc{save_to_memory, save_to_file}
\ No newline at end of file +save :: proc{save_to_memory, save_to_file} + +load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { + context.allocator = allocator + options := options + + if .alpha_premultiply in options { + return nil, .Unsupported_Option + } + + 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} + } + + header := image.read_data(ctx, image.TGA_Header) or_return + + // Header checks + if header.data_type_code != DATATYPE_UNCOMPRESSED_RGB { + return nil, .Unsupported_Format + } + if header.bits_per_pixel!=24 && header.bits_per_pixel!=32 { + return nil, .Unsupported_Format + } + if ( header.image_descriptor & IMAGE_DESCRIPTOR_INTERLEAVING_MASK ) != 0 { + return nil, .Unsupported_Format + } + + if (int(header.dimensions[0])*int(header.dimensions[1])) > image.MAX_DIMENSIONS { + return nil, .Image_Dimensions_Too_Large + } + + if img == nil { + img = new(Image) + } + + if .return_metadata in options { + info := new(image.TGA_Info) + info.header = header + img.metadata = info + } + src_channels := int(header.bits_per_pixel)/8 + img.which = .TGA + img.channels = .alpha_add_if_missing in options ? 4: src_channels + img.channels = .alpha_drop_if_present in options ? 3: img.channels + + img.depth = 8 + img.width = int(header.dimensions[0]) + img.height = int(header.dimensions[1]) + + if .do_not_decompress_image in options { + return img, nil + } + + // skip id + if _, e := compress.read_slice(ctx, int(header.id_length)); e!= .None { + destroy(img) + return nil, .Corrupt + } + + if !resize(&img.pixels.buf, img.channels * img.width * img.height) { + destroy(img) + return nil, .Unable_To_Allocate_Or_Resize + } + + origin_is_topleft := (header.image_descriptor & IMAGE_DESCRIPTOR_TOPLEFT_MASK ) != 0 + for y in 0..<img.height { + line := origin_is_topleft ? y : img.height-y-1 + dst := mem.ptr_offset(mem.raw_data(img.pixels.buf), line*img.width*img.channels) + for x in 0..<img.width { + src, err := compress.read_slice(ctx, src_channels) + if err!=.None { + destroy(img) + return nil, .Corrupt + } + dst[2] = src[0] + dst[1] = src[1] + dst[0] = src[2] + if img.channels==4 { + if src_channels==4 { + dst[3] = src[3] + } else { + dst[3] = 255 + } + } + dst = mem.ptr_offset(dst, img.channels) + } + } + return img, nil +} + +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 +} + +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 + } +} + +load :: proc{load_from_file, load_from_bytes, load_from_context} + +destroy :: proc(img: ^Image) { + if img == nil { + return + } + + bytes.buffer_destroy(&img.pixels) + if v, ok := img.metadata.(^image.TGA_Info); ok { + free(v) + } + + free(img) +} + +DATATYPE_UNCOMPRESSED_RGB :: 0x2 +IMAGE_DESCRIPTOR_INTERLEAVING_MASK :: (1<<6) | (1<<7) +IMAGE_DESCRIPTOR_TOPLEFT_MASK :: 1<<5 + +@(init, private) +_register :: proc() { + image.register(.TGA, load_from_bytes, destroy) +}
\ No newline at end of file |