diff --git a/core/image/tga/tga.odin b/core/image/tga/tga.odin index af5b07ef5..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,29 +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 { - // Supported formats: RGB(A), RGB(A) RLE - case .Compressed_RGB: - rle_encoding = true - case .Uncompressed_RGB: + #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 .No_Image_Data: - return nil, .Unsupported_Format - case .Uncompressed_Color_Mapped: - return nil, .Unsupported_Format - case .Uncompressed_Black_White: - return nil, .Unsupported_Format - case .Compressed_Color_Mapped: - return nil, .Unsupported_Format - case .Compressed_Black_White: - return nil, .Unsupported_Format - case: - return nil, .Unsupported_Format + 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 } @@ -184,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 @@ -196,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)) @@ -204,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 if origin_is_left else -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_top else -1