diff --git a/core/image/common.odin b/core/image/common.odin index c79344445..7c8df3d57 100644 --- a/core/image/common.odin +++ b/core/image/common.odin @@ -379,8 +379,13 @@ QOI_Info :: struct { } TGA_Data_Type :: enum u8 { - Uncompressed_RGB = 2, - Compressed_RBB = 10, + No_Image_Data = 0, + Uncompressed_Color_Mapped = 1, + Uncompressed_RGB = 2, + Uncompressed_Black_White = 3, + Compressed_Color_Mapped = 9, + Compressed_RGB = 10, + Compressed_Black_White = 11, } TGA_Header :: struct #packed { diff --git a/core/image/tga/tga.odin b/core/image/tga/tga.odin index d257d06ff..d8b200b67 100644 --- a/core/image/tga/tga.odin +++ b/core/image/tga/tga.odin @@ -17,8 +17,6 @@ import "core:bytes" import "core:os" import "core:compress" import "core:strings" -import "core:fmt" -_ :: fmt // TODO: alpha_premultiply support @@ -142,15 +140,58 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a header := image.read_data(ctx, image.TGA_Header) or_return // Header checks - rle_encoding := false + rle_encoding := false + color_mapped := false + src_channels := 0 + dest_depth := header.bits_per_pixel + dest_channels := 0 - switch header.data_type_code { - case .Compressed_RBB: rle_encoding = true - case .Uncompressed_RGB: - case: return nil, .Unsupported_Format + #partial switch header.data_type_code { + // Supported formats: RGB(A), RGB(A) RLE + case .Compressed_RGB: + rle_encoding = true + case .Uncompressed_RGB: + // Intentionally blank + case .Uncompressed_Color_Mapped: + color_mapped = true + + case: + return nil, .Unsupported_Format } - if header.bits_per_pixel != 24 && header.bits_per_pixel != 32 { + if color_mapped { + if header.color_map_type != 1 { + return nil, .Unsupported_Format + } + dest_depth = header.color_map_depth + + // Expect LUT entry index to be 8 bits + if header.bits_per_pixel != 8 || header.color_map_origin != 0 || header.color_map_length > 256 { + return nil, .Unsupported_Format + } + } + + switch dest_depth { + case 15: // B5G5R5 + src_channels = 2 + dest_channels = 3 + if color_mapped { + return nil, .Unsupported_Format + } + case 16: // B5G5R5A1 + src_channels = 2 + dest_channels = 3 // Alpha bit is dodgy in TGA, so we ignore it. + if color_mapped { + return nil, .Unsupported_Format + } + case 24: // RGB8 + src_channels = 3 if !color_mapped else 1 + dest_channels = 3 + case 32: // RGBA8 + src_channels = 4 if !color_mapped else 1 + dest_channels = 4 + + case: return nil, .Unsupported_Format } @@ -170,9 +211,8 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a destroy(img) } - src_channels := int(header.bits_per_pixel) / 8 img.which = .TGA - img.channels = 4 if .alpha_add_if_missing in options else src_channels + img.channels = 4 if .alpha_add_if_missing in options else dest_channels img.channels = 3 if .alpha_drop_if_present in options else img.channels img.depth = 8 @@ -182,7 +222,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a // Read Image ID if present image_id := "" if _id, e := compress.read_slice(ctx, int(header.id_length)); e != .None { - return nil, .Corrupt + return img, .Corrupt } else { if .return_metadata in options { id := strings.trim_right_null(string(_id)) @@ -190,6 +230,32 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a } } + color_map := make([]RGBA_Pixel, header.color_map_length) + defer delete(color_map) + + if color_mapped { + switch header.color_map_depth { + case 24: + for i in 0..> 5) & 31) << 3 + r := u8((v >> 10) & 31) << 3 + pixel = {r, g, b, 255} + case 3: + pixel = {src[2], src[1], src[0], 255} + case 4: + pixel = {src[2], src[1], src[0], src[3]} + case: + return img, .Corrupt } } // Write pixel - copy(img.pixels.buf[offset:], pixel[:img.channels]) - offset += img.channels + copy(img.pixels.buf[offset:], pixel[:dest_channels]) + offset += dest_channels if origin_is_left else -dest_channels rle_repetition_count -= 1 } - line += 1 if origin_is_topleft else -1 + line += 1 if origin_is_top else -1 } return img, nil } @@ -310,7 +382,8 @@ destroy :: proc(img: ^Image) { } IMAGE_DESCRIPTOR_INTERLEAVING_MASK :: (1<<6) | (1<<7) -IMAGE_DESCRIPTOR_TOPLEFT_MASK :: 1<<5 +IMAGE_DESCRIPTOR_RIGHT_MASK :: 1<<4 +IMAGE_DESCRIPTOR_TOP_MASK :: 1<<5 @(init, private) _register :: proc() {