-module(tga). -export([load/1]). -export([test_uncompressed/0, test_compressed/0, test_enhanced/0, time_uncompressed/0, time_compressed/0]). % === test functions ========================================================== test_uncompressed() -> load("test_images/uncompressed.tga"). test_compressed() -> load("test_images/compressed.tga"). test_enhanced() -> load("test_images/enhanced.tga"). time_uncompressed() -> element(1, timer:tc(tga, test_uncompressed, [])). time_compressed() -> element(1, timer:tc(tga, test_compressed, [])). % === main load & decode functions ============================================ load(Filename) -> {ok, Data} = file:read_file(Filename), decode(Data). decode(Data) -> <> = Data, % io:fwrite( % "~w bytes read~n" % "id len = ~w~n" % "map type = ~w~n" % "image type = ~w~n" % "x origin = ~w~n" % "y origin = ~w~n" % "height = ~w~n" % "width = ~w~n" % "pixel depth = ~w~n" % "image desc = ~w~n" % "rest = ~w bytes~n", % [size(Data), Id_len, Map_type, Img_type, X_origin, Y_origin, H, W, % Depth, Img_desc, size(Rest)]), 0 = Map_type, % no palette Rows = case Img_type of 2 -> decode_raw_image(Rest, W, H); _ -> decode_rle_image(Rest, W, H) end, {W, H, Rows}. % === decode uncompressed image =============================================== decode_raw_image(Pixels, W, H) -> decode_raw_image(Pixels, W*3, H, [], <<>>). decode_raw_image(_, _, 0, Acc, _) -> Acc; decode_raw_image(Pixels, W, H, Acc, Prev_row) -> <> = Pixels, % If this row is identical to the previous row, then there's no need to % process it. Simply get the last processed row. This significantly % speeds up images with many empty rows. Converted = if Row == Prev_row -> hd(Acc); true -> decode_raw_row(Row) end, decode_raw_image(Rest, W, H-1, [Converted|Acc], Row). decode_raw_row(Row) -> S = size(Row) div 3, case count_left(Row) of L when L == S -> {L, L, <<>>}; L -> R = count_right(Row), Len = S - L - R, <<_:L/binary-unit:24, Pixels:Len/binary-unit:24, _/binary>> = Row, {L, R, decode_rgb(Pixels)} end. % === decode rle compressed image ============================================= % The simplest method is to fully uncompress the image and pass the result % to decode_raw_image, but this makes compressed images over twice as slow % to process. An implementation of such a naive method is at the bottom of % the file. % % This code uses the inherent structure of an RLE image to speed things up % (from over 2x slower to 22% faster than the identical compressed image % in my test case). % % Here's what happens per row: % % 1. A row is broken into a list of runs and raw data. Adjacent runs % of the same color are combined, as the longest run in the TGA format is % 128. Pixels of 255,0,255 are represented by the atom "clear". % % 2. A transparent run may be split across a run and the raw data that % follows it (consider a run of 129 transparent pixels followed by other % data; the 129th pixel will be part of that data). These cases are % removed by the coalesce function. % % 3. The list of runs and raw data is expanded into {Left, Right, Data} by % the expand function, where Left and Right are leading and trailing runs % of transparent pixels and Data is a binary containing the middle data. decode_rle_image(Pixels, W, H) -> decode_rle_image(Pixels, W, H, []). decode_rle_image(Pixels, W, 0, Acc) -> Acc; decode_rle_image(Pixels, W, H, Acc) -> {Row, Rest} = decode_rle_row(Pixels, W), decode_rle_image(Rest, W, H-1, [Row|Acc]). decode_rle_row(Pixels, W) -> reduce_rle_row(Pixels, W, []). reduce_rle_row(Rest, 0, Acc) -> {expand(coalesce(lists:reverse(Acc))), Rest}; reduce_rle_row(<<1:1, Len:7, R:8, G:8, B:8, Rest/binary>>, W, Acc) -> Run_len = Len + 1, continue_rle_row(Rest, W - Run_len, Acc, R, G, B, Run_len); reduce_rle_row(<<0:1, Len:7, Rest/binary>>, W, Acc) -> Run_len = Len + 1, Bytes = Run_len * 3, <> = Rest, reduce_rle_row(Rest2, W - Run_len, [Run | Acc]). % Now that we've found a run, collect all adjacent runs of the same color, % combining them into one run. Turn color of [255,0,255] into atom "clear". continue_rle_row(<<1:1, Len:7, R:8, G:8, B:8, Rest/binary>>, W, Acc, R, G, B, N) when W > 0 -> Run_len = Len + 1, continue_rle_row(Rest, W - Run_len, Acc, R, G, B, N + Run_len); continue_rle_row(Rest, W, Acc, 255,0,255, N) -> reduce_rle_row(Rest, W, [{N, clear} | Acc]); continue_rle_row(Rest, W, Acc, R, G, B, N) -> reduce_rle_row(Rest, W, [{N, [R,G,B]} | Acc]). % Compressed image decoding utilities. coalesce(L) -> combine(split(L)). split([<<255,0,255, _/binary>>=B | T]) -> % Leading transparency in a literal run. N = count_left(B), Bytes = N * 3, <<_:Bytes/binary, B2/binary>> = B, [{N, clear}, B2 | split(T)]; split([<>]) -> % Trailing transparency in a literal run at the end of a row. case count_right(B) of 0 -> []; N -> Bytes = size(B) - (N * 3), <> = B, [B2, {N, clear}] end; split([H|T]) -> [H | split(T)]; split([]) -> []. combine([{Run1, Color}, {Run2, Color} | T]) -> [{Run1 + Run2, Color} | combine(T)]; combine([<<>>|T]) -> combine(T); combine([H|T]) -> [H | combine(T)]; combine([]) -> []. expand([{Run, clear}]) -> {Run, Run, <<>>}; % empty row expand([{Run, clear} | T]) -> expand(T, Run, []); % leading transparency expand(L) -> expand(L, 0, []). % no leading transparency expand(L, Left, Acc) -> case L of % trailing transparency [{Run, clear}] -> {Left, Run, list_to_binary(lists:reverse(Acc))}; % middle transparency [{Run, clear} | T] -> expand(T, Left, [lists:duplicate(Run, [0,0,0,0]) | Acc]); % other run of pixels [{Run, [R,G,B]} | T] -> expand(T, Left, [lists:duplicate(Run, [R,G,B,255]) | Acc]); % raw pixels [H|T] when binary(H) -> expand(T, Left, [decode_rgb(H) | Acc]); [] -> {Left, 0, list_to_binary(lists:reverse(Acc))} end. % === utilities =============================================================== % Turn 24-bit pixels in a binary into 32-bit pixels, handling transparency. decode_rgb(Pixels) -> list_to_binary(decode_rgb1(binary_to_list(Pixels))). decode_rgb1([255,0,255 | Rest]) -> [0,0,0,0 | decode_rgb1(Rest)]; decode_rgb1([R,G,B | Rest]) -> [R,G,B,255 | decode_rgb1(Rest)]; decode_rgb1([]) -> []. % Count pixels of 255,0,255 on the left side of a binary row. count_left(B) -> count_left(B, 0). count_left(<<255,0,255, Rest/binary>>, N) -> count_left(Rest, N+1); count_left(B, N) -> N. % Count pixels of 255,0,255 on the right side of a binary row. % Note that that Left can go negative, but this is legal in the binary syntax. count_right(B) -> count_right(B, size(B)-3, 0). count_right(B, Left, N) -> case B of <<_:Left/binary, 255,0,255, _/binary>> -> count_right(B, Left-3, N+1); _ -> N end. % Simple processing of a compressed TGA by uncompressing it then calling % decode_raw_image. % %decode_rle_image(Pixels, W, H) -> % decode_raw_image(decode_rle_image1(Pixels, W*H, []), W, H). %decode_rle_image1(_, 0, Acc) -> list_to_binary(lists:reverse(Acc)); %decode_rle_image1(<<1:1, Len:7, R:8, G:8, B:8, Rest/binary>>, N, Acc) -> % Run_len = Len + 1, % decode_rle_image1(Rest, N - Run_len, [lists:duplicate(Run_len, [R,G,B]) | Acc]); %decode_rle_image1(<<0:1, Len:7, Rest/binary>>, N, Acc) -> % Run_len = Len + 1, % Bytes = Run_len * 3, % <> = Rest, % decode_rle_image1(Rest2, N - Run_len, [Run | Acc]).