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: ^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