1%%
2%%  e3d__dds.erl --
3%%
4%%     Functions for reading .dds files.
5%%
6%%  Copyright (c) 2018 Dan Gudmundsson
7%%
8%%  See the file "license.terms" for information on usage and redistribution
9%%  of this file, and for a DISCLAIMER OF ALL WARRANTIES.
10%%
11
12-module(e3d__dds).
13
14-export([load/2, save/3]).
15-export([format_error/1]).
16-include("e3d_image.hrl").
17
18% debug
19-export([rgb16/1,rgb32/1,rgb16to32/1, bc1/1, bc2/1]).
20
21format_error(unsupported_format) ->
22    "Unsupported format or bad DDS file";
23format_error({unsupported_format, _Line}) ->
24    "Unsupported format or bad DDS file";
25format_error(bad_image_specifiction) ->
26    "Bad image specification".
27
28-define(DW, 32/little-unsigned).
29-define(PF_SIZE, (4*8)).
30
31-define(IS(Flags, Flag), ((Flags band (Flag)) > 0)).
32
33-define(ERROR(What), throw({What, ?LINE})).
34
35save(#e3d_image{type=InType}=Image0, FileName, _Opts) ->
36    Image = e3d_image:convert(Image0, InType, 1, lower_left),
37    try
38        IoData = save_1(Image),
39        file:write_file(FileName, IoData)
40    catch throw:Error -> {error, Error};
41          _:Error:ST ->
42            io:format("~p ~p ~p~n", [?MODULE, Error, ST]),
43            {error, internal_error}
44    end.
45
46save_1(#e3d_image{width=W, height=H, type=Type, bytes_pp=Bpp, image=Img})
47  when Bpp =:= 3; Bpp =:= 4 ->
48    Pitch = W*Bpp,
49    Flags = 16#1 bor 16#2 bor 16#4 bor 16#1000,
50    PFBin = case Type of
51                r8g8b8a8 ->
52                    PFFlags = 16#1 bor 16#40,
53                    RM = 16#FF, GM = 16#FF00, BM = 16#FF0000, AM = 16#FF000000,
54                    <<32:?DW, PFFlags:?DW, 0:?DW, (Bpp*8):?DW, RM:?DW, GM:?DW, BM:?DW, AM:?DW>>;
55                r8g8b8 ->
56                    PFFlags = 16#40,
57                    RM = 16#FF, GM = 16#FF00, BM = 16#FF0000, AM = 16#FF000000,
58                    <<32:?DW, PFFlags:?DW, 0:?DW, (Bpp*8):?DW, RM:?DW, GM:?DW, BM:?DW, AM:?DW>>;
59                b8g8r8a8 ->
60                    PFFlags = 16#1 bor 16#40,
61                    RM = 16#FF0000, GM = 16#FF00, BM = 16#FF, AM = 16#FF000000,
62                    <<32:?DW, PFFlags:?DW, 0:?DW, (Bpp*8):?DW, RM:?DW, GM:?DW, BM:?DW, AM:?DW>>;
63                b8g8r8 ->
64                    PFFlags = 16#40,
65                    RM = 16#FF0000, GM = 16#FF00, BM = 16#FF, AM = 16#FF000000,
66                    <<32:?DW, PFFlags:?DW, 0:?DW, (Bpp*8):?DW, RM:?DW, GM:?DW, BM:?DW, AM:?DW>>
67            end,
68    32 = byte_size(PFBin),
69    Header = <<"DDS ", 124:?DW, Flags:?DW,
70               H:?DW, W:?DW, Pitch:?DW,
71               0:?DW, 1:?DW, 0:(11*4*8),
72               PFBin:?PF_SIZE/binary,
73               16#1000:?DW, 0:?DW, 0:?DW, 0:?DW, 0:?DW
74             >>,
75    124 = byte_size(Header) - 4,
76    [Header, Img].
77
78
79load(FileName, _Opts) ->
80    try case file:read_file(FileName) of
81            {ok, <<"DDS ", 124:?DW, Flags:?DW, H:?DW, W:?DW, PorLS:?DW,
82                   _Depth:?DW, MipMapN:?DW, _:(11*4)/binary,
83                   PFBin:?PF_SIZE/binary,
84                   Caps:?DW, Caps2:?DW, _:?DW, _:?DW, _:?DW,
85                   Rest/binary>>} ->
86                Im0 = #{w=>W, h=>H},
87                Im1 = parse_pitch(Flags, PorLS, Im0),
88                Im2 = parse_pf(PFBin, Im1),
89                Im3 = parse_caps(Caps, Caps2, MipMapN, Im2),
90                {Bin, Im4} = parse_dxt10(Rest, Im3),
91                get_images(Bin, Im4);
92            Error ->
93                Error
94        end
95    catch throw:Err -> Err
96    end.
97
98parse_pitch(Flags, PitchOrLs, Im0) ->
99    if ?IS(Flags, 16#8) -> Im0#{pitch=>PitchOrLs};
100       ?IS(Flags, 16#80000) -> Im0#{linearsz=>PitchOrLs};
101       true -> Im0
102    end.
103
104parse_pf(<<32:?DW,Flags:?DW,FCC:4/binary,BitC:?DW,RM:?DW,GM:?DW,BM:?DW,AM:?DW>>, Im0) ->
105    if ?IS(Flags,16#2) -> ?ERROR(unsupported_format);
106       ?IS(Flags,16#200) -> ?ERROR(unsupported_format);
107       ?IS(Flags,16#20000) -> ?ERROR(unsupported_format);
108       true -> ok
109    end,
110    PF0 = if ?IS(Flags,16#1) -> Im0#{a_mask=>AM};
111             true -> Im0
112          end,
113    PF1 = if ?IS(Flags,16#04) -> PF0#{fourCC => FCC};
114             true -> PF0
115          end,
116    PF2 = if ?IS(Flags,16#40) -> PF1#{bytes_pp=>BitC div 8, r_mask=>RM, g_mask=>GM, b_mask=>BM};
117             true -> PF1
118          end,
119    PF2;
120parse_pf(_, _) -> ?ERROR(unsupported_format).
121
122parse_caps(Caps1, Caps2, MipMapN, Im0) ->
123    Im = if ?IS(Caps1, 16#400000) -> Im0#{mipmaps=>MipMapN};
124            true -> Im0#{mipmaps=>1}
125         end,
126    All = 16#400 bor 16#800 bor 16#1000 bor 16#2000 bor 16#4000 bor 16#8000,
127    if ?IS(Caps2, 16#200), ?IS(Caps2, All) -> Im#{cubemap=>true};
128       ?IS(Caps2, 16#200) -> ?ERROR(unsupported_format);
129       true -> Im#{cubemap=>false}
130    end.
131
132parse_dxt10(<<Format:?DW, Dim:?DW, Flags1:?DW, Sz:?DW, _Flags2:?DW, Rest/binary>>,
133            #{fourCC := <<"DX10">>}=Im) ->
134    if Dim =/= 3 -> ?ERROR(unsupported_format);
135       Sz =/= 1 -> ?ERROR(unsupported_format);
136       true -> ok
137    end,
138    Cubemap = ?IS(Flags1, 16#4),
139    {Rest, Im#{fourCC:=dxgi_format(Format), cubemap=>Cubemap, count=>Sz}};
140%% Convert old fourCC to newer format
141parse_dxt10(Rest, #{fourCC := <<"DXT", Variant:8>>}=Im) ->
142    Alg = case Variant of
143              $1 -> {bc1, uint};
144              $2 -> {bc2, uint};
145              $3 -> {bc2, uint};
146              $4 -> {bc3, uint};
147              $5 -> {bc3, uint}
148          end,
149    {Rest, Im#{fourCC:=Alg}};
150parse_dxt10(Rest, #{fourCC := <<"ATI1">>}=Im) ->
151    {Rest, Im#{fourCC:={bc4, uint}}};
152parse_dxt10(Rest, #{fourCC := <<"BC4U">>}=Im) ->
153    {Rest, Im#{fourCC:={bc4, uint}}};
154parse_dxt10(Rest, #{fourCC := <<"BC4S">>}=Im) ->
155    {Rest, Im#{fourCC:={bc4, sint}}};
156parse_dxt10(Rest, #{fourCC := <<"ATI2">>}=Im) ->
157    {Rest, Im#{fourCC:={bc5, uint}}};
158parse_dxt10(Rest, #{fourCC := <<"BC5U">>}=Im) ->
159    {Rest, Im#{fourCC:={bc5, uint}}};
160parse_dxt10(Rest, #{fourCC := <<"BC5S">>}=Im) ->
161    {Rest, Im#{fourCC:={bc5, sint}}};
162%% Non-spec usage
163parse_dxt10(Rest, #{fourCC := <<"RGBG">>}=Im) ->
164    {Rest, Im#{fourCC:={r8g8_b8g8,uint}}};
165parse_dxt10(Rest, #{fourCC := <<"GRGB">>}=Im) ->
166    {Rest, Im#{fourCC:={g8r8_g8b8,uint}}};
167parse_dxt10(Rest, #{fourCC := <<Enum:32/unsigned-little>>}=Im) ->
168    %% From https://docs.microsoft.com/en-us/windows/uwp/gaming/complete-code-for-ddstextureloader
169    Format = case Enum of
170                 36 -> %% D3DFMT_A16B16G16R16
171                     dxgi_format(11); %DXGI_FORMAT_R16G16B16A16_UNORM
172                 110 -> %% D3DFMT_Q16W16V16U16
173                     dxgi_format(13); %DXGI_FORMAT_R16G16B16A16_SNORM;
174                 111 -> %% D3DFMT_R16F
175                     dxgi_format(54); % DXGI_FORMAT_R16_FLOAT;
176                 112 -> %% D3DFMT_G16R16F
177                     dxgi_format(34); % DXGI_FORMAT_R16G16_FLOAT;
178                 113 -> %% D3DFMT_A16B16G16R16F
179                     dxgi_format(11); % DXGI_FORMAT_R16G16B16A16_FLOAT;
180                 114 -> %% D3DFMT_R32F
181                     dxgi_format(41); % DXGI_FORMAT_R32_FLOAT;
182                 115 -> %% D3DFMT_G32R32F
183                     dxgi_format(16); % DXGI_FORMAT_R32G32_FLOAT;
184                 116 -> %% D3DFMT_A32B32G32R32F
185                     dxgi_format(2); % DXGI_FORMAT_R32G32B32A32_FLOAT;
186                 _ ->
187                     dxgi_format(0)
188             end,
189    {Rest, Im#{fourCC:=Format}};
190parse_dxt10(Rest, #{fourCC := _}=Im) ->
191    {Rest, Im};
192parse_dxt10(Rest, Im) ->
193    {Rest, Im#{fourCC=>none}}.
194
195get_images(Bin, #{w:=W, h:=H, mipmaps:=MM, cubemap:=false}=Opts) ->
196    {Type, Bpp, Div, BSz, Decomp} = comp_alg(Opts),
197    {[{Image,_,_,_}|MMs],_Pad} = get_mipmaps(Bin, W, H, Div, BSz, 0, MM-1, Decomp, []),
198    #e3d_image{width=W, height=H, bytes_pp=Bpp, type=Type,
199               order=upper_left, alignment=1, image=Image,
200               extra=add_mm(MMs)};
201
202get_images(Bin0, #{w:=W, h:=H, mipmaps:=MM, cubemap:=true} = Opts) ->
203    {Type, Bpp, Div, BSz, Decomp} = comp_alg(Opts),
204    Get = fun(Dir, Bin) ->
205                  {[{Image,_,_,_}|MMs], Rest} = get_mipmaps(Bin, W, H, Div, BSz, 0, MM-1, Decomp, []),
206                  {#{dir=>Dir, tx=>Image, mipmaps=>MMs}, Rest}
207          end,
208    {CubeTx, _Pad} = lists:mapfoldl(Get, Bin0, [pos_x, neg_x, pos_y, neg_y, pos_z, neg_z]),
209    [#{dir:=pos_x,tx:=Image, mipmaps:=Mipmaps}|Rest] = CubeTx,
210    #e3d_image{width=W, height=H, bytes_pp=Bpp, type=Type,
211               order=upper_left, alignment=1, image=Image,
212               extra=[{cubemaps, Rest}|add_mm(Mipmaps)]}.
213
214get_mipmaps(Bin, W, H, Div, BSz, Level, Level, Decomp, Acc) ->
215    Sz = b_size(W, H, Div, BSz),
216    <<MM:Sz/binary, Rest/binary>> = Bin,
217    Image = Decomp(MM,W,H),
218    {lists:reverse([{Image,W,H,Level}|Acc]), Rest};
219get_mipmaps(Bin, W, H, Div, BSz, Level, Max, Decomp, Acc) when Level < Max ->
220    Sz = b_size(W, H, Div, BSz),
221    <<MM:Sz/binary, Rest/binary>> = Bin,
222    Image = Decomp(MM,W,H),
223    get_mipmaps(Rest, max(1,W div 2), max(1,H div 2), Div, BSz, Level+1, Max, Decomp,
224                [{Image,W,H,Level}|Acc]).
225
226b_size(W, H, Div, BSz) ->
227    ((W+Div-1) div Div) * ((H+Div-1) div Div) *BSz.
228
229add_mm([]) -> [];
230add_mm(MMs) -> [{mipmaps, MMs}].
231
232type(#{b_mask:=16#FF, a_mask:=_, bytes_pp:=4}) -> b8g8r8a8;
233type(#{r_mask:=16#FF, a_mask:=_, bytes_pp:=4}) -> r8g8b8a8;
234type(#{b_mask:=16#FF, bytes_pp:=3}) -> b8g8r8;
235type(#{r_mask:=16#FF, bytes_pp:=3}) -> r8g8b8;
236type({Atom, uint}) -> Atom;
237type({Atom, undefined}) -> Atom;  %% Just keep it (we don't know)
238type({Atom, sint})  -> list_to_atom(atom_to_list(Atom) ++ "s");
239type({Atom, float}) -> list_to_atom(atom_to_list(Atom) ++ "f");
240type(_) -> ?ERROR(unsupported_format).
241
242comp_alg(#{fourCC:={bc1, uint}}) ->
243    Expand = fun(Block) -> bc1(Block) end,
244    Decompress = fun(Compressed, MmW, MmH) ->
245                         uncompress(Compressed, MmW, MmH, 8, Expand, 32)
246                 end,
247    {r8g8b8a8, 4, 4, 8, Decompress};
248comp_alg(#{fourCC:={bc2, uint}}) ->
249    Expand = fun(Block) -> bc2(Block) end,
250    Decompress = fun(Compressed, MmW, MmH) ->
251                         uncompress(Compressed, MmW, MmH, 16, Expand, 32)
252                 end,
253    {r8g8b8a8, 4, 4, 16, Decompress};
254comp_alg(#{fourCC:={bc3, uint}}) ->
255    Expand = fun(Block) -> bc3(Block) end,
256    Decompress = fun(Compressed, MmW, MmH) ->
257                         uncompress(Compressed, MmW, MmH, 16, Expand, 32)
258                 end,
259    {r8g8b8a8, 4, 4, 16, Decompress};
260comp_alg(#{fourCC:={bc4, Signed}}) ->
261    {Expand, Type} = case Signed of
262                         uint -> {fun(Block) -> bc4(Block) end, g8};
263                         sint -> {fun(Block) -> bc4s(Block) end, g8s}
264                     end,
265    Decompress = fun(Compressed, MmW, MmH) ->
266                         uncompress(Compressed, MmW, MmH, 8, Expand, 8)
267                 end,
268    {Type, 1, 4, 8, Decompress};
269comp_alg(#{fourCC:={bc5, Signed}}) ->
270    {Expand, Type} = case Signed of
271                         uint -> {fun(Block) -> bc5(Block) end, r8g8};
272                         sint -> {fun(Block) -> bc5s(Block) end, r8g8s}
273                     end,
274    Decompress = fun(Compressed, MmW, MmH) ->
275                         uncompress(Compressed, MmW, MmH, 16, Expand, 16)
276                 end,
277    {Type, 2, 4, 16, Decompress};
278
279%% Not compressed
280comp_alg(#{fourCC:={r16g16b16a16,_}=T}) ->
281    {type(T), 8, 1, 8, fun(Orig, _, _) -> Orig end};
282comp_alg(#{fourCC:={r32g32b32a32,_}=T}) ->
283    {type(T), 16, 1, 16, fun(Orig, _, _) -> Orig end};
284comp_alg(#{fourCC:={r32g32b32,_}=T}) ->
285    {type(T), 12, 1, 12, fun(Orig, _, _) -> Orig end};
286comp_alg(#{fourCC:={r8g8b8a8,_}=T}) ->
287    {type(T), 4, 1, 4, fun(Orig, _, _) -> Orig end};
288comp_alg(#{fourCC:={b8g8r8a8,_}}) ->
289    {b8g8r8a8, 4, 1, 4, fun(Orig, _, _) -> Orig end};
290comp_alg(#{fourCC:={b8g8r8x8,_}}) ->
291    {b8g8r8a8, 4, 1, 4, fun(Orig, _, _) -> Orig end};
292comp_alg(#{fourCC:={r8g8,_}=T}) ->
293    {type(T), 2, 1, 2, fun(Orig, _, _) -> Orig end};
294comp_alg(#{fourCC:={r32,_}=T}) ->
295    {type(T), 4, 1, 4, fun(Orig, _, _) -> Orig end};
296comp_alg(#{fourCC:={r16,_}=T}) ->
297    {type(T), 2, 1, 2, fun(Orig, _, _) -> Orig end};
298comp_alg(#{fourCC:={r8,_}=T}) ->
299    {type(T), 1, 1, 1, fun(Orig, _, _) -> Orig end};
300comp_alg(#{fourCC:={a8,_}=T}) ->
301    {type(T), 1, 1, 1, fun(Orig, _, _) -> Orig end};
302
303comp_alg(#{fourCC:=none, bytes_pp:=Bpp} = Opts) ->
304    {type(Opts), Bpp, 1, Bpp, fun(Orig, _, _) -> Orig end};
305
306comp_alg(Variant) ->
307    io:format("~p: unsupported_format / compression: ~p~n",[?MODULE, Variant]),
308    ?ERROR(unsupported_compression).
309
310uncompress(Compressed, W, H, BSz, Expand, BiPP) ->
311    CRowSz = ((W+3) div 4) * BSz,
312    CRows = [CRow || <<CRow:CRowSz/binary>> <= Compressed],
313    Decomp = fun(CRow, Rows) ->
314                     Uncomp = [Expand(Bits) || <<Bits:BSz/binary>> <= CRow],
315                     join_rows(Uncomp, Rows, BiPP)
316             end,
317    Decompressed = lists:foldl(Decomp, <<>>, CRows),
318    %% Crop image to actual size (for non multiple of 4 pixels images or mipmaps)
319    ImgSz = W*H*(BiPP div 8),
320    <<Img:ImgSz/binary, _/binary>> = Decompressed,
321    Img.
322
323join_rows(R4x4, Prev, BiPP) ->
324    [R1,R2,R3,R4] = join_block(R4x4, <<>>, <<>>, <<>>, <<>>, BiPP),
325    <<Prev/binary, R1/binary, R2/binary, R3/binary, R4/binary>>.
326
327join_block([[C00,C01,C02,C03, C10,C11,C12,C13, C20,C21,C22,C23, C30,C31,C32,C33]|Tl],
328           R1,R2,R3,R4, BiPP) ->
329    join_block(Tl,
330               <<R1/binary, C00:BiPP,C01:BiPP,C02:BiPP,C03:BiPP>>,
331               <<R2/binary, C10:BiPP,C11:BiPP,C12:BiPP,C13:BiPP>>,
332               <<R3/binary, C20:BiPP,C21:BiPP,C22:BiPP,C23:BiPP>>,
333               <<R4/binary, C30:BiPP,C31:BiPP,C32:BiPP,C33:BiPP>>, BiPP);
334join_block([], R1,R2,R3,R4, _) ->
335    [R1,R2,R3,R4].
336
337-define(EXP5TO8R(Col16),((((Col16) bsr 8) band 16#f8) bor (((Col16) bsr 13) band 16#7))).
338-define(EXP6TO8G(Col16),((((Col16) bsr 3) band 16#fc) bor (((Col16) bsr  9) band 16#3))).
339-define(EXP5TO8B(Col16),((((Col16) bsl 3) band 16#f8) bor (((Col16) bsr  2) band 16#7))).
340
341-define(RGB(R,G,B), ((R bsl 24) bor (G bsl 16) bor (B bsl 8) bor 255)).
342-define(RGB(R,G,B,A), ((R bsl 24) bor (G bsl 16) bor (B bsl 8) bor A)).
343
344bc1(<<C0:16/unsigned-little, C1:16/unsigned-little,
345       Inds:32/unsigned-little>>) ->
346    Cs = case C0 > C1 of
347             true ->
348                 C2R = (2*?EXP5TO8R(C0)+?EXP5TO8R(C1)) div 3,
349                 C2G = (2*?EXP6TO8G(C0)+?EXP6TO8G(C1)) div 3,
350                 C2B = (2*?EXP5TO8B(C0)+?EXP5TO8B(C1)) div 3,
351                 C3R = (?EXP5TO8R(C0)+2*?EXP5TO8R(C1)) div 3,
352                 C3G = (?EXP6TO8G(C0)+2*?EXP6TO8G(C1)) div 3,
353                 C3B = (?EXP5TO8B(C0)+2*?EXP5TO8B(C1)) div 3,
354                 {?RGB(?EXP5TO8R(C0), ?EXP6TO8G(C0), ?EXP5TO8B(C0)),
355                  ?RGB(?EXP5TO8R(C1), ?EXP6TO8G(C1), ?EXP5TO8B(C1)),
356                  ?RGB(C2R,C2G,C2B),
357                  ?RGB(C3R,C3G,C3B)};
358             false ->
359                 C2R = (?EXP5TO8R(C0)+?EXP5TO8R(C1)) div 2,
360                 C2G = (?EXP6TO8G(C0)+?EXP6TO8G(C1)) div 2,
361                 C2B = (?EXP5TO8B(C0)+?EXP5TO8B(C1)) div 2,
362                 {?RGB(?EXP5TO8R(C0), ?EXP6TO8G(C0), ?EXP5TO8B(C0)),
363                  ?RGB(?EXP5TO8R(C1), ?EXP6TO8G(C1), ?EXP5TO8B(C1)),
364                  ?RGB(C2R,C2G,C2B),
365                  0}
366         end,
367    bc1(Inds, 16, Cs).
368
369bc1(I, N, Cs) when N > 0 ->
370    [element((I band 3)+1, Cs) | bc1(I bsr 2, N-1, Cs)];
371bc1(_, 0, _) -> [].
372
373bc2(<<A:64/unsigned-little,
374       C0:16/unsigned-little, C1:16/unsigned-little,
375       Inds:32/unsigned-little>>) ->
376    C2R = (2*?EXP5TO8R(C0)+?EXP5TO8R(C1)) div 3,
377    C2G = (2*?EXP6TO8G(C0)+?EXP6TO8G(C1)) div 3,
378    C2B = (2*?EXP5TO8B(C0)+?EXP5TO8B(C1)) div 3,
379    C3R = (?EXP5TO8R(C0)+2*?EXP5TO8R(C1)) div 3,
380    C3G = (?EXP6TO8G(C0)+2*?EXP6TO8G(C1)) div 3,
381    C3B = (?EXP5TO8B(C0)+2*?EXP5TO8B(C1)) div 3,
382    Cs = {?RGB(?EXP5TO8R(C0), ?EXP6TO8G(C0), ?EXP5TO8B(C0)),
383          ?RGB(?EXP5TO8R(C1), ?EXP6TO8G(C1), ?EXP5TO8B(C1)),
384          ?RGB(C2R,C2G,C2B),
385          ?RGB(C3R,C3G,C3B)},
386    bc2(Inds, A, 16, Cs).
387
388bc2(I, A0, N, Cs) when N > 0 ->
389    A = A0 band 15,
390    RGBA = element((I band 3)+1, Cs) band (((1 bsl 24)-1 bsl 8) bor (A bsl 4 bor A)),
391    [RGBA | bc2(I bsr 2, A0  bsr 4, N-1, Cs)];
392bc2(_, _, 0, _) -> [].
393
394bc3(<<AC0:8, AC1:8, AInds:48/unsigned-little,
395       C0:16/unsigned-little, C1:16/unsigned-little,
396       Inds:32/unsigned-little>>) ->
397    C2R = (2*?EXP5TO8R(C0)+?EXP5TO8R(C1)) div 3,
398    C2G = (2*?EXP6TO8G(C0)+?EXP6TO8G(C1)) div 3,
399    C2B = (2*?EXP5TO8B(C0)+?EXP5TO8B(C1)) div 3,
400    C3R = (?EXP5TO8R(C0)+2*?EXP5TO8R(C1)) div 3,
401    C3G = (?EXP6TO8G(C0)+2*?EXP6TO8G(C1)) div 3,
402    C3B = (?EXP5TO8B(C0)+2*?EXP5TO8B(C1)) div 3,
403    Cs = {?RGB(?EXP5TO8R(C0), ?EXP6TO8G(C0), ?EXP5TO8B(C0)),
404          ?RGB(?EXP5TO8R(C1), ?EXP6TO8G(C1), ?EXP5TO8B(C1)),
405          ?RGB(C2R,C2G,C2B),
406          ?RGB(C3R,C3G,C3B)},
407    As = as(AC0,AC1),
408    bc3(Inds, AInds, 16, Cs, As).
409
410bc3(I, AI, N, Cs, As) when N > 0 ->
411    A = element((AI band 7)+1, As),
412    [element((I band 3)+1, Cs) band (((1 bsl 24)-1 bsl 8) bor A) |
413     bc3(I bsr 2, AI bsr 3, N-1, Cs, As)];
414bc3(_, _, _, _, _) ->
415    [].
416
417bc4(<<AC0:8/unsigned, AC1:8/unsigned, AInds:48/unsigned-little>>) ->
418    As = as(AC0,AC1),
419    bc4(AInds, 16, As).
420bc4s(<<AC0:8/signed, AC1:8/signed, AInds:48/unsigned-little>>) ->
421    As = as(AC0,AC1),
422    bc4(AInds, 16, As).
423
424bc4(AI, N, As) when N > 0 ->
425    A = element((AI band 7)+1, As),
426    [A | bc4(AI bsr 3, N-1, As)];
427bc4(_, _, _) ->
428    [].
429
430bc5(<<AC0:8/unsigned, AC1:8/unsigned, AInds1:48/unsigned-little,
431      AC3:8/unsigned, AC4:8/unsigned, AInds2:48/unsigned-little>>) ->
432    As1 = as(AC0,AC1),
433    As2 = as(AC3,AC4),
434    bc5(AInds1, AInds2, 16, As1, As2).
435bc5s(<<AC0:8/signed, AC1:8/signed, AInds1:48/unsigned-little,
436       AC3:8/signed, AC4:8/signed, AInds2:48/unsigned-little>>) ->
437    As1 = as(AC0,AC1),
438    As2 = as(AC3,AC4),
439    bc5(AInds1, AInds2, 16, As1, As2).
440
441bc5(A1, B1, N, As1, As2) when N > 0 ->
442    A = element((A1 band 7)+1, As1),
443    B = element((B1 band 7)+1, As2),
444    [(A bsl 8) bor B | bc5(A1 bsr 3, B1 bsr 3, N-1, As1, As2)];
445bc5(_, _, _, _, _) ->
446    [].
447
448as(AC0, AC1) when AC0 > AC1 ->
449    {AC0, AC1, (6*AC0+1*AC1) div 7, (5*AC0+2*AC1) div 7,
450     (4*AC0+3*AC1) div 7, (3*AC0+4*AC1) div 7,
451     (2*AC0+5*AC1) div 7, (1*AC0+6*AC1) div 7};
452as(AC0, AC1) ->
453    {AC0, AC1, (4*AC0+1*AC1) div 5, (3*AC0+2*AC1) div 5,
454     (2*AC0+3*AC1) div 5, (1*AC0+4*AC1) div 5, 0, 255}.
455
456%% Debug
457rgb16(<<RGB:16/unsigned-little>>) ->
458    rgb16(RGB);
459rgb16(RGB) ->
460    {?EXP5TO8R(RGB), ?EXP6TO8G(RGB), ?EXP5TO8B(RGB)}.
461
462rgb32({R,G,B}) -> <<?RGB(R,G,B):32>>.
463
464rgb16to32(RGB) ->
465    rgb32(rgb16(RGB)).
466
467dxgi_format(Format) ->
468    case Format of
469        0 -> {unknown, undefined};            % DXGI_FORMAT_UNKNOWN                     = 0,
470        1 -> {r32g32b32a32,undefined};        % DXGI_FORMAT_R32G32B32A32_TYPELESS       = 1,
471        2 -> {r32g32b32a32,float};            % DXGI_FORMAT_R32G32B32A32_FLOAT          = 2,
472        3 -> {r32g32b32a32,uint};             % DXGI_FORMAT_R32G32B32A32_UINT           = 3,
473        4 -> {r32g32b32a32,sint};             % DXGI_FORMAT_R32G32B32A32_SINT           = 4,
474        5 -> {r32g32b32,undefined};           % DXGI_FORMAT_R32G32B32_TYPELESS          = 5,
475        6 -> {r32g32b32,float};               % DXGI_FORMAT_R32G32B32_FLOAT             = 6,
476        7 -> {r32g32b32,uint};                % DXGI_FORMAT_R32G32B32_UINT              = 7,
477        8 -> {r32g32b32,sint};                % DXGI_FORMAT_R32G32B32_SINT              = 8,
478        9 -> {r16g16b16a16,undefined};        % DXGI_FORMAT_R16G16B16A16_TYPELESS       = 9,
479        10 -> {r16g16b16a16,float};           % DXGI_FORMAT_R16G16B16A16_FLOAT          = 10,
480        11 -> {r16g16b16a16,uint};            % DXGI_FORMAT_R16G16B16A16_UNORM          = 11,
481        12 -> {r16g16b16a16,uint};            % DXGI_FORMAT_R16G16B16A16_UINT           = 12,
482        13 -> {r16g16b16a16,sint};            % DXGI_FORMAT_R16G16B16A16_SNORM          = 13,
483        14 -> {r16g16b16a16,sint};            % DXGI_FORMAT_R16G16B16A16_SINT           = 14,
484        15 -> {r32g32,undefined};             % DXGI_FORMAT_R32G32_TYPELESS             = 15,
485        16 -> {r32g32,float};                 % DXGI_FORMAT_R32G32_FLOAT                = 16,
486        17 -> {r32g32,uint};                  % DXGI_FORMAT_R32G32_UINT                 = 17,
487        18 -> {r32g32,sint};                  % DXGI_FORMAT_R32G32_SINT                 = 18,
488        19 -> {r32g8x24,undefined};           % DXGI_FORMAT_R32G8X24_TYPELESS           = 19,
489        20 -> {d32_float_s8x24,uint};         % DXGI_FORMAT_D32_FLOAT_S8X24_UINT        = 20,
490        21 -> {r32_float_x8x24,undefined};    % DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS    = 21,
491        22 -> {x32_undefined_g8x24,uint};     % DXGI_FORMAT_X32_TYPELESS_G8X24_UINT     = 22,
492        23 -> {r10g10b10a2,undefined};        % DXGI_FORMAT_R10G10B10A2_TYPELESS        = 23,
493        24 -> {r10g10b10a2,uint};             % DXGI_FORMAT_R10G10B10A2_UNORM           = 24,
494        25 -> {r10g10b10a2,uint};             % DXGI_FORMAT_R10G10B10A2_UINT            = 25,
495        26 -> {r11g11b10,float};              % DXGI_FORMAT_R11G11B10_FLOAT             = 26,
496        27 -> {r8g8b8a8,undefined};           % DXGI_FORMAT_R8G8B8A8_TYPELESS           = 27,
497        28 -> {r8g8b8a8,uint};                % DXGI_FORMAT_R8G8B8A8_UNORM              = 28,
498        29 -> {r8g8b8a8,uint};                % DXGI_FORMAT_R8G8B8A8_UNORM_SRGB         = 29,
499        30 -> {r8g8b8a8,uint};                % DXGI_FORMAT_R8G8B8A8_UINT               = 30,
500        31 -> {r8g8b8a8,sint};                % DXGI_FORMAT_R8G8B8A8_SNORM              = 31,
501        32 -> {r8g8b8a8,sint};                % DXGI_FORMAT_R8G8B8A8_SINT               = 32,
502        33 -> {r16g16,undefined};             % DXGI_FORMAT_R16G16_TYPELESS             = 33,
503        34 -> {r16g16,float};                 % DXGI_FORMAT_R16G16_FLOAT                = 34,
504        35 -> {r16g16,uint};                  % DXGI_FORMAT_R16G16_UNORM                = 35,
505        36 -> {r16g16,uint};                  % DXGI_FORMAT_R16G16_UINT                 = 36,
506        37 -> {r16g16,sint};                  % DXGI_FORMAT_R16G16_SNORM                = 37,
507        38 -> {r16g16,sint};                  % DXGI_FORMAT_R16G16_SINT                 = 38,
508        39 -> {r32,undefined};                % DXGI_FORMAT_R32_TYPELESS                = 39,
509        40 -> {d32,float};                    % DXGI_FORMAT_D32_FLOAT                   = 40,
510        41 -> {r32,float};                    % DXGI_FORMAT_R32_FLOAT                   = 41,
511        42 -> {r32,uint};                     % DXGI_FORMAT_R32_UINT                    = 42,
512        43 -> {r32,sint};                     % DXGI_FORMAT_R32_SINT                    = 43,
513        44 -> {r24g8,undefined};              % DXGI_FORMAT_R24G8_TYPELESS              = 44,
514        45 -> {d24_s8,uint};                  % DXGI_FORMAT_D24_UNORM_S8_UINT           = 45,
515        46 -> {r24_x8,undefined};             % DXGI_FORMAT_R24_UNORM_X8_TYPELESS       = 46,
516        47 -> {x24_undefined_g8,uint};        % DXGI_FORMAT_X24_TYPELESS_G8_UINT        = 47,
517        48 -> {r8g8,undefined};               % DXGI_FORMAT_R8G8_TYPELESS               = 48,
518        49 -> {r8g8,uint};                    % DXGI_FORMAT_R8G8_UNORM                  = 49,
519        50 -> {r8g8,uint};                    % DXGI_FORMAT_R8G8_UINT                   = 50,
520        51 -> {r8g8,sint};                    % DXGI_FORMAT_R8G8_SNORM                  = 51,
521        52 -> {r8g8,sint};                    % DXGI_FORMAT_R8G8_SINT                   = 52,
522        53 -> {r16,undefined};                % DXGI_FORMAT_R16_TYPELESS                = 53,
523        54 -> {r16,float};                    % DXGI_FORMAT_R16_FLOAT                   = 54,
524        55 -> {d16,uint};                     % DXGI_FORMAT_D16_UNORM                   = 55,
525        56 -> {r16,uint};                     % DXGI_FORMAT_R16_UNORM                   = 56,
526        57 -> {r16,uint};                     % DXGI_FORMAT_R16_UINT                    = 57,
527        58 -> {r16,sint};                     % DXGI_FORMAT_R16_SNORM                   = 58,
528        59 -> {r16,sint};                     % DXGI_FORMAT_R16_SINT                    = 59,
529        60 -> {r8,undefined};                 % DXGI_FORMAT_R8_TYPELESS                 = 60,
530        61 -> {r8,uint};                      % DXGI_FORMAT_R8_UNORM                    = 61,
531        62 -> {r8,uint};                      % DXGI_FORMAT_R8_UINT                     = 62,
532        63 -> {r8,sint};                      % DXGI_FORMAT_R8_SNORM                    = 63,
533        64 -> {r8,sint};                      % DXGI_FORMAT_R8_SINT                     = 64,
534        65 -> {a8,uint};                      % DXGI_FORMAT_A8_UNORM                    = 65,
535        66 -> {r1,uint};                      % DXGI_FORMAT_R1_UNORM                    = 66,
536        67 -> {r9g9b9e5,sharedexp};           % DXGI_FORMAT_R9G9B9E5_SHAREDEXP          = 67,
537        68 -> {r8g8_b8g8,uint};               % DXGI_FORMAT_R8G8_B8G8_UNORM             = 68,
538        69 -> {g8r8_g8b8,uint};               % DXGI_FORMAT_G8R8_G8B8_UNORM             = 69,
539        70 -> {bc1,undefined};                % DXGI_FORMAT_BC1_TYPELESS                = 70,
540        71 -> {bc1,uint};                     % DXGI_FORMAT_BC1_UNORM                   = 71,
541        72 -> {bc1,uint};                     % DXGI_FORMAT_BC1_UNORM_SRGB              = 72,
542        73 -> {bc2,undefined};                % DXGI_FORMAT_BC2_TYPELESS                = 73,
543        74 -> {bc2,uint};                     % DXGI_FORMAT_BC2_UNORM                   = 74,
544        75 -> {bc2,uint};                     % DXGI_FORMAT_BC2_UNORM_SRGB              = 75,
545        76 -> {bc3,undefined};                % DXGI_FORMAT_BC3_TYPELESS                = 76,
546        77 -> {bc3,uint};                     % DXGI_FORMAT_BC3_UNORM                   = 77,
547        78 -> {bc3,uint};                     % DXGI_FORMAT_BC3_UNORM_SRGB              = 78,
548        79 -> {bc4,undefined};                % DXGI_FORMAT_BC4_TYPELESS                = 79,
549        80 -> {bc4,uint};                     % DXGI_FORMAT_BC4_UNORM                   = 80,
550        81 -> {bc4,sint};                     % DXGI_FORMAT_BC4_SNORM                   = 81,
551        82 -> {bc5,undefined};                % DXGI_FORMAT_BC5_TYPELESS                = 82,
552        83 -> {bc5,uint};                     % DXGI_FORMAT_BC5_UNORM                   = 83,
553        84 -> {bc5,sint};                     % DXGI_FORMAT_BC5_SNORM                   = 84,
554        85 -> {b5g6r5,uint};                  % DXGI_FORMAT_B5G6R5_UNORM                = 85,
555        86 -> {b5g5r5a1,uint};                % DXGI_FORMAT_B5G5R5A1_UNORM              = 86,
556        87 -> {b8g8r8a8,uint};                % DXGI_FORMAT_B8G8R8A8_UNORM              = 87,
557        88 -> {b8g8r8x8,uint};                % DXGI_FORMAT_B8G8R8X8_UNORM              = 88,
558        89 -> {r10g10b10_xr_bias_a2,uint};    % DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM  = 89,
559        90 -> {b8g8r8a8,undefined};           % DXGI_FORMAT_B8G8R8A8_TYPELESS           = 90,
560        91 -> {b8g8r8a8,uint};                % DXGI_FORMAT_B8G8R8A8_UNORM_SRGB         = 91,
561        92 -> {b8g8r8x8,undefined};           % DXGI_FORMAT_B8G8R8X8_TYPELESS           = 92,
562        93 -> {b8g8r8x8,uint};                % DXGI_FORMAT_B8G8R8X8_UNORM_SRGB         = 93,
563        94 -> {bc6h,undefined};               % DXGI_FORMAT_BC6H_TYPELESS               = 94,
564        95 -> {bc6h,uf16};                    % DXGI_FORMAT_BC6H_UF16                   = 95,
565        96 -> {bc6h,sf16};                    % DXGI_FORMAT_BC6H_SF16                   = 96,
566        97 -> {bc7,undefing};                 % DXGI_FORMAT_BC7_TYPELESS                = 97,
567        98 -> {bc7,uint};                     % DXGI_FORMAT_BC7_UNORM                   = 98,
568        99 -> {bc7,uint};                     % DXGI_FORMAT_BC7_UNORM_SRGB              = 99,
569        100 -> {ayuv, uint};                  % DXGI_FORMAT_AYUV                        = 100,
570        101 -> {y410, uint};                  % DXGI_FORMAT_Y410                        = 101,
571        102 -> {y416, uint};                  % DXGI_FORMAT_Y416                        = 102,
572        103 -> {nv12, uint};                  % DXGI_FORMAT_NV12                        = 103,
573        104 -> {p010, uint};                  % DXGI_FORMAT_P010                        = 104,
574        105 -> {p016, uint};                  % DXGI_FORMAT_P016                        = 105,
575        106 -> {'420_opaque', uint};          % DXGI_FORMAT_420_OPAQUE                  = 106,
576        107 -> {yuy2, uint};                  % DXGI_FORMAT_YUY2                        = 107,
577        108 -> {y210, uint};                  % DXGI_FORMAT_Y210                        = 108,
578        109 -> {y216, uint};                  % DXGI_FORMAT_Y216                        = 109,
579        110 -> {nv11, uint};                  % DXGI_FORMAT_NV11                        = 110,
580        111 -> {ai44, uint};                  % DXGI_FORMAT_AI44                        = 111,
581        112 -> {ia44, uint};                  % DXGI_FORMAT_IA44                        = 112,
582        113 -> {p8, uint};                    % DXGI_FORMAT_P8                          = 113,
583        114 -> {a8p8, uint};                  % DXGI_FORMAT_A8P8                        = 114,
584        115 -> {b4g4r4a4, uint};              % DXGI_FORMAT_B4G4R4A4_UNORM              = 115,
585        130 -> {p208, uint};                  % DXGI_FORMAT_P208                        = 130,
586        131 -> {v208, uint};                  % DXGI_FORMAT_V208                        = 131,
587        132 -> {v408, uint};                  % DXGI_FORMAT_V408                        = 132,
588        _ -> {unknown, undefined}
589    end.
590