Browse Source

Add support for basic TGA loading

Benoit Jacquier 3 years ago
parent
commit
00f2e911a7
2 changed files with 151 additions and 1 deletions
  1. 6 0
      core/image/common.odin
  2. 145 1
      core/image/tga/tga.odin

+ 6 - 0
core/image/common.odin

@@ -61,6 +61,7 @@ Image_Metadata :: union #shared_nil {
 	^Netpbm_Info,
 	^Netpbm_Info,
 	^PNG_Info,
 	^PNG_Info,
 	^QOI_Info,
 	^QOI_Info,
+	^TGA_Info,
 }
 }
 
 
 
 
@@ -168,6 +169,7 @@ Error :: union #shared_nil {
 
 
 General_Image_Error :: enum {
 General_Image_Error :: enum {
 	None = 0,
 	None = 0,
+	Unsupported_Option,
 	// File I/O
 	// File I/O
 	Unable_To_Read_File,
 	Unable_To_Read_File,
 	Unable_To_Write_File,
 	Unable_To_Write_File,
@@ -390,6 +392,10 @@ TGA_Header :: struct #packed {
 }
 }
 #assert(size_of(TGA_Header) == 18)
 #assert(size_of(TGA_Header) == 18)
 
 
+TGA_Info :: struct {
+	header: TGA_Header,
+}
+
 // Function to help with image buffer calculations
 // Function to help with image buffer calculations
 compute_buffer_size :: proc(width, height, channels, depth: int, extra_row_bytes := int(0)) -> (size: int) {
 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
 	size = ((((channels * width * depth) + 7) >> 3) + extra_row_bytes) * height

+ 145 - 1
core/image/tga/tga.odin

@@ -14,6 +14,11 @@ import "core:mem"
 import "core:image"
 import "core:image"
 import "core:bytes"
 import "core:bytes"
 import "core:os"
 import "core:os"
+import "core:compress"
+
+// TODO: alpha_premultiply support
+// TODO: RLE decompression
+
 
 
 Error   :: image.Error
 Error   :: image.Error
 Image   :: image.Image
 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
 	return nil if write_ok else .Unable_To_Write_File
 }
 }
 
 
-save :: proc{save_to_memory, save_to_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)
+}