Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

951 lines (844 sloc) 27.599 kb
%%
%% wings_image.erl --
%%
%% This module manages images.
%%
%% Copyright (c) 2003-2008 Bjorn Gustavsson
%%
%% See the file "license.terms" for information on usage and redistribution
%% of this file, and for a DISCLAIMER OF ALL WARRANTIES.
%%
%% $Id$
%%
-module(wings_image).
-export([init/0,init_opengl/0,
from_file/1,new/2,new_temp/2,create/1,
rename/2,txid/1,info/1,images/0,screenshot/0,
bumpid/1, default/1,
is_normalmap/1, normal_cubemapid/0, pnoiseid/0,
next_id/0,delete_older/1,delete_from/1,delete/1,
update/2,update_filename/2,draw_preview/5,
window/1]).
-export([image_formats/0,image_read/1,image_write/1]).
-export([loop/1]).
-define(NEED_OPENGL, 1).
-include("wings.hrl").
-include("e3d_image.hrl").
-import(lists, [reverse/1,foreach/2,flatten/1]).
-define(DEFAULT, '$Default Img').
init() ->
spawn_opt(fun server/0, [link,{fullsweep_after,0}]).
init_opengl() ->
req(init_opengl).
%%%
%%% Interface against plug-ins.
%%%
image_formats() ->
wings_plugin:call_ui({image,formats,[]}).
image_read(Ps) ->
CurrDir = wings_pref:get_value(current_directory),
OptDir = proplists:get_value(opt_dir, Ps, undefined),
AbsFile = proplists:get_value(filename, Ps),
FileName = filename:basename(AbsFile),
Dirs = filename:split(filename:dirname(AbsFile)),
case wings_plugin:call_ui({image,read,Ps}) of
{error, enoent} ->
try_relative_paths(Dirs,FileName,OptDir,CurrDir,Ps);
Ok = #e3d_image{} ->
Ok#e3d_image{filename=AbsFile};
Other ->
Other
end.
try_relative_paths(Dirs,FileName,undefined,CurrDir,Ps) ->
try_relative_paths(CurrDir,Dirs,FileName,Ps);
try_relative_paths(Dirs,FileName,OptDir,CurrDir,Ps) ->
case try_relative_paths(OptDir,Dirs,FileName,Ps) of
{error, enoent} ->
try_relative_paths(Dirs,FileName,undefined,CurrDir,Ps);
Other ->
Other
end.
try_relative_paths(Start, Rel, File, Ps0) ->
Abs = case Rel of
[] -> filename:join(Start,File);
_ -> filename:join(filename:join(Start,filename:join(Rel)),File)
end,
Ps = lists:keyreplace(filename,1,Ps0,{filename,Abs}),
case wings_plugin:call_ui({image,read,Ps}) of
Err = {error, enoent} ->
case Rel of
[] -> Err;
[_|Rest] -> try_relative_paths(Start,Rest,File,Ps0)
end;
Ok = #e3d_image{} ->
Ok#e3d_image{filename=Abs};
Other ->
Other
end.
image_write(Ps) ->
case catch wings_plugin:call_ui({image,write,Ps}) of
{'EXIT',Reason} ->
{error,{none,?MODULE,{crash,Reason}}};
Result -> Result
end.
%%%
%%% Client API.
%%%
from_file(Filename) ->
Props = [{filename,Filename},{alignment,1}],
case image_read(Props) of
#e3d_image{}=Image ->
Name = filename:rootname(filename:basename(Filename)),
req({new,Image#e3d_image{name=Name},false});
{error,_}=Error -> Error
end.
new(Name, E3DImage) ->
req({new,E3DImage#e3d_image{name=Name},false}).
new_temp(Name, E3DImage) ->
req({new,E3DImage#e3d_image{name=Name},true}).
create(St) ->
create_image(),
St.
rename(Id, NewName) ->
req({rename,Id,NewName}).
txid(Id) ->
req({txid,Id}, false).
screenshot() ->
{W,H} = wings_wm:top_size(),
gl:pixelStorei(?GL_PACK_ALIGNMENT, 1),
gl:readBuffer(?GL_FRONT),
Mem = sdl_util:alloc(W*H*3, ?GL_UNSIGNED_BYTE),
gl:readPixels(0, 0, W, H, ?GL_RGB, ?GL_UNSIGNED_BYTE, Mem),
ImageBin = sdl_util:getBin(Mem),
Image = #e3d_image{image=ImageBin,width=W,height=H},
Id = new_temp(?__(1,"<<Screenshot>>"), Image),
wings_image:window(Id).
is_normalmap(Id) ->
req({is_normalmap,Id},false).
bumpid(Id) ->
req({bumpid,Id}, false).
normal_cubemapid() ->
req(normalCM, false).
pnoiseid() ->
req(pnoise, false).
default(Type) ->
req({default,Type}, false).
info(Id) ->
req({info,Id}, false).
images() ->
req(images, false).
next_id() ->
req(next_id, false).
delete_older(Id) ->
req({delete_older,Id}).
delete_from(Id) ->
req({delete_from,Id}).
delete(Id) ->
req({delete,Id}).
update(Id, Image) ->
req({update,Id,Image}).
update_filename(Id, Filename) ->
req({update_filename,Id,Filename}).
draw_preview(X, Y, W, H, Id) ->
req({draw_preview,X,Y,W,H,Id}, false).
req(Req) ->
req(Req, true).
req(Req, Notify) ->
Self = self(),
Ref = make_ref(),
wings_image ! {Self,Ref,Req},
receive
{Ref,Answer} ->
case Notify of
false -> ok;
true -> wings_wm:notify(image_change)
end,
Answer
end.
%%%
%%% Server implementation.
%%%
%%% Reason for using a server: Convenient encapsulation of images.
%%% We'll get an entire process dictionary to use for texture ids.
%%%
-record(ist,
{next=0, %Next image ID.
images %All images (gb_trees).
}).
server() ->
register(wings_image, self()),
loop(#ist{images=gb_trees:empty()}).
loop(S0) ->
receive
{Client,Ref,Req} ->
case handle(Req, S0) of
#ist{}=S ->
Client ! {Ref,ok};
{Resp,S} ->
Client ! {Ref,Resp}
end,
loop(S);
reload ->
?MODULE:loop(S0);
Other ->
exit({bad_message_to_wings_image,Other})
end.
handle(init_opengl, #ist{images=Images}=S) ->
erase(), %% Forget all textures!
foreach(fun({Id,Image}) ->
make_texture(Id, Image)
end, gb_trees:to_list(Images)),
init_background_tx(),
S;
handle({new,#e3d_image{name=Name0}=Im0,false}, #ist{next=Id,images=Images0}=S) ->
Name = make_unique(Name0, Images0),
Im = maybe_convert(Im0#e3d_image{name=Name}),
Images = gb_trees:insert(Id, Im, Images0),
make_texture(Id, Im),
{Id,S#ist{next=Id+1,images=Images}};
handle({new,#e3d_image{name=Name}=Im,true}, #ist{images=Images}=S0) ->
Prev = [Id || {Id,#e3d_image{name=N}} <- gb_trees:to_list(Images),
N =:= Name],
case Prev of
[] ->
handle({new,Im,false}, S0);
[Id] ->
S = handle({delete,Id}, S0),
handle({new,Im,false}, S)
end;
handle({rename,Id,Name0}, #ist{images=Images0}=S) ->
Name = make_unique(Name0, gb_trees:delete(Id, Images0)),
Im0 = gb_trees:get(Id, Images0),
Im = Im0#e3d_image{name=Name},
Images = gb_trees:update(Id, Im, Images0),
{Id,S#ist{images=Images}};
handle({txid,Id}, S) ->
{case get(Id) of
undefined -> none;
TxId -> TxId
end,S};
handle({bumpid,Id}, S) ->
{case get({Id,bump}) of
undefined ->
create_bump(Id,undefined,S);
TxId ->
TxId
end,S};
handle({is_normalmap,Id},S) ->
{case {get({Id,bump}),get(Id)} of
{undefined,undefined} -> none;
{undefined, ImId} -> create_bump(Id,ImId,S);
{TxId,_} -> TxId
end,S};
handle(normalCM, S) ->
{case get(normalCM) of
undefined ->
create_normal_cube_map();
TxId ->
TxId
end,S};
handle(pnoise, S) ->
{case get(pnoise) of
undefined ->
create_pnoise();
TxId ->
TxId
end,S};
handle({default,all},S) ->
All = [diffuse, bump, gloss],
Ids = [{Type,element(1, handle({default,Type},S))} || Type <- All],
{Ids, S};
handle({default,Type},S) ->
{case get({?DEFAULT, Type}) of
undefined ->
create_default(Type);
TxId ->
TxId
end,S};
handle({info,Id}, #ist{images=Images}=S) ->
case gb_trees:lookup(Id, Images) of
{value,E3D} -> {E3D,S};
none -> {none,S}
end;
handle(images, #ist{images=Images}=S) ->
{gb_trees:to_list(Images),S};
handle(next_id, #ist{next=Id}=S) ->
{Id,S};
handle({delete,Id}, S) ->
delete(Id, S);
handle({delete_older,Id}, S) ->
delete_older(Id, S);
handle({delete_from,Id}, S) ->
delete_from(Id, S);
handle({update,Id,Image}, S) ->
do_update(Id, Image, S);
handle({update_filename,Id,NewName}, #ist{images=Images0}=S) ->
Im0 = gb_trees:get(Id, Images0),
Im = Im0#e3d_image{filename=NewName},
Images = gb_trees:update(Id, Im, Images0),
S#ist{images=Images};
handle({draw_preview,X,Y,W,H,Id}, S) ->
{case get(Id) of
undefined -> error;
TxId -> draw_image(X, Y, W, H, TxId)
end,S}.
create_bump(Id, BumpId, #ist{images=Images0}) ->
delete_bump(Id), %% update case..
case gb_trees:lookup(Id, Images0) of
{value, E3D} ->
gl:pushAttrib(?GL_TEXTURE_BIT),
case get(Id) of
BumpId ->
gl:bindTexture(?GL_TEXTURE_2D, BumpId),
Image = case e3d_image:convert(maybe_scale(E3D),r8g8b8,1,lower_left) of
E3D -> E3D;
New = #e3d_image{width=W,height=H,image=Bits} ->
gl:texImage2D(?GL_TEXTURE_2D,0,?GL_RGB,W,H,0,?GL_RGB,
?GL_UNSIGNED_BYTE,Bits),
New
end,
MipMapFun = fun() -> e3d_image:buildNormalMipmaps(Image) end,
MipMaps = worker_process(MipMapFun),
TxId = BumpId;
_ ->
%% Scale ?? 4 is used in the only example I've seen.
Img = e3d_image:convert(maybe_scale(E3D), r8g8b8, 1, lower_left),
{#e3d_image{width=W,height=H,image=Bits},MipMaps}
= e3d_image:height2normal(Img, 4, true),
[TxId] = gl:genTextures(1),
gl:bindTexture(?GL_TEXTURE_2D, TxId),
gl:texImage2D(?GL_TEXTURE_2D,0,?GL_RGB,W,H,0,?GL_RGB,
?GL_UNSIGNED_BYTE,Bits)
end,
put({Id,bump}, TxId),
update_mipmaps(TxId,MipMaps),
gl:popAttrib(),
TxId;
_ ->
none
end.
create_default(bump) ->
Img = #e3d_image{width=1,height=1, % Default direction
image= <<127,127,255>>}, % Normal points to +Z
make_texture({?DEFAULT,bump},Img);
create_default(Type) when Type == diffuse; Type == gloss ->
Img = #e3d_image{width=1,height=1, % Default diffuse/spec
image= <<255,255,255>>}, % White and full specular.
make_texture({?DEFAULT,Type},Img);
create_default(Type) ->
io:format(?__(1,"~p Don't know about the type ~p; ignoring\n"),
[?MODULE,Type]),
none.
create_normal_cube_map() ->
case wings_gl:is_ext('GL_ARB_texture_cube_map') of
true ->
[CubeMap] = gl:genTextures(1),
gl:bindTexture(?GL_TEXTURE_CUBE_MAP, CubeMap),
make_normalize_vector_cubemap(32),
put(normalCM,CubeMap),
CubeMap;
false ->
none
end.
create_pnoise() ->
try
[NoiseMap] = gl:genTextures(1),
gl:bindTexture(?GL_TEXTURE_3D, NoiseMap),
Map = pnoise:s_map3d(128),
Assert = byte_size(Map),
Assert = 128*128*128*4,
gl:texParameteri(?GL_TEXTURE_3D, ?GL_TEXTURE_MAG_FILTER, ?GL_LINEAR),
case wings_gl:is_ext({1,4},'GL_SGIS_generate_mipmap') of
true ->
gl:texParameteri(?GL_TEXTURE_3D, ?GL_GENERATE_MIPMAP,
?GL_TRUE),
gl:texParameteri(?GL_TEXTURE_3D, ?GL_TEXTURE_MIN_FILTER,
?GL_NEAREST_MIPMAP_LINEAR);
false ->
gl:texParameteri(?GL_TEXTURE_3D, ?GL_TEXTURE_MIN_FILTER,
?GL_LINEAR)
end,
gl:texParameteri(?GL_TEXTURE_3D, ?GL_TEXTURE_WRAP_S,
?GL_MIRRORED_REPEAT),
gl:texParameteri(?GL_TEXTURE_3D, ?GL_TEXTURE_WRAP_T,
?GL_MIRRORED_REPEAT),
gl:texParameteri(?GL_TEXTURE_3D, ?GL_TEXTURE_WRAP_R,
?GL_MIRRORED_REPEAT),
gl:texImage3D(?GL_TEXTURE_3D, 0, ?GL_RGBA, 128, 128, 128, 0,
?GL_RGBA, ?GL_UNSIGNED_BYTE, Map),
put(pnoise,NoiseMap),
NoiseMap
catch _:_ ->
none
end.
update_mipmaps(TxId, MipMaps) ->
gl:enable(?GL_TEXTURE_2D),
gl:bindTexture(?GL_TEXTURE_2D, TxId),
gl:texParameteri(?GL_TEXTURE_2D, ?GL_TEXTURE_MAG_FILTER, ?GL_LINEAR),
gl:texParameteri(?GL_TEXTURE_2D, ?GL_TEXTURE_MIN_FILTER, ?GL_LINEAR_MIPMAP_LINEAR),
gl:texParameteri(?GL_TEXTURE_2D, ?GL_TEXTURE_WRAP_S, ?GL_REPEAT),
gl:texParameteri(?GL_TEXTURE_2D, ?GL_TEXTURE_WRAP_T, ?GL_REPEAT),
Load = fun({Level,MW,MH, Bin}) ->
gl:texImage2D(?GL_TEXTURE_2D,Level,?GL_RGB,MW,MH,0,
?GL_RGB, ?GL_UNSIGNED_BYTE, Bin)
end,
[Load(MM) || MM <- MipMaps].
maybe_convert(#e3d_image{type=Type0,order=Order}=Im) ->
case {img_type(Type0),Order} of
{Type0,lower_left} -> Im;
{Type,_} -> e3d_image:convert(Im, Type, 1, lower_left)
end.
img_type(b8g8r8) -> r8g8b8;
img_type(b8g8r8a8) -> r8g8b8a8;
img_type(Type) -> Type.
init_background_tx() ->
White = [255,255,255],
Grey = [204,204,204],
EightWhite = pattern_repeat(8, White),
EightGrey = pattern_repeat(8, Grey),
B0 = [pattern_repeat(8, [EightGrey|EightWhite])|
pattern_repeat(8, [EightWhite|EightGrey])],
B = list_to_binary(B0),
Im = #e3d_image{width=16,height=16,image=B},
put(background, init_texture(Im)).
make_texture(Id, Image) ->
TxId = init_texture(Image),
put(Id, TxId),
TxId.
init_texture(Image) ->
[TxId] = gl:genTextures(1),
init_texture(Image, TxId).
init_texture(Image0, TxId) ->
Image = maybe_scale(Image0),
#e3d_image{width=W,height=H,image=Bits} = Image,
gl:pushAttrib(?GL_TEXTURE_BIT),
gl:enable(?GL_TEXTURE_2D),
gl:bindTexture(?GL_TEXTURE_2D, TxId),
case wings_gl:is_ext({1,4},'GL_SGIS_generate_mipmap') of
true ->
gl:texParameteri(?GL_TEXTURE_2D, ?GL_GENERATE_MIPMAP, ?GL_TRUE),
gl:texParameteri(?GL_TEXTURE_2D, ?GL_TEXTURE_MIN_FILTER,
?GL_LINEAR_MIPMAP_LINEAR);
false ->
gl:texParameteri(?GL_TEXTURE_2D, ?GL_TEXTURE_MIN_FILTER, ?GL_LINEAR)
end,
gl:texParameteri(?GL_TEXTURE_2D, ?GL_TEXTURE_MAG_FILTER, ?GL_LINEAR),
gl:texParameteri(?GL_TEXTURE_2D, ?GL_TEXTURE_WRAP_S, ?GL_REPEAT),
gl:texParameteri(?GL_TEXTURE_2D, ?GL_TEXTURE_WRAP_T, ?GL_REPEAT),
Format = texture_format(Image),
gl:texImage2D(?GL_TEXTURE_2D, 0, internal_format(Format),
W, H, 0, Format, ?GL_UNSIGNED_BYTE, Bits),
gl:popAttrib(),
TxId.
maybe_scale(#e3d_image{width=W0,height=H0,bytes_pp=BytesPerPixel,
image=Bits0}=Image) ->
%% case wings_gl:is_ext({2,0}, 'GL_ARB_texture_non_power_of_two') of
%% Aarg ATI doesn't support ARB_NPOT textures, though it report GL_VER >= 2.0
case wings_gl:is_ext('GL_ARB_texture_non_power_of_two') of
true -> Image;
false ->
case {nearest_power_two(W0),nearest_power_two(H0)} of
{W0,H0} -> Image;
{W,H} ->
Out = sdl_util:alloc(BytesPerPixel*W*H, ?GL_UNSIGNED_BYTE),
Format = texture_format(Image),
glu:scaleImage(Format, W0, H0, ?GL_UNSIGNED_BYTE,
Bits0, W, H, ?GL_UNSIGNED_BYTE, Out),
Bits = sdl_util:getBin(Out),
Image#e3d_image{width=W,height=H,image=Bits}
end
end.
nearest_power_two(N) when (N band -N) =:= N -> N;
nearest_power_two(N) -> nearest_power_two(N, 1).
nearest_power_two(N, B) when B > N -> B bsr 1;
nearest_power_two(N, B) -> nearest_power_two(N, B bsl 1).
texture_format(#e3d_image{type=r8g8b8}) -> ?GL_RGB;
texture_format(#e3d_image{type=r8g8b8a8}) -> ?GL_RGBA;
texture_format(#e3d_image{type=b8g8r8}) -> ?GL_BGR;
texture_format(#e3d_image{type=b8g8r8a8}) -> ?GL_BGRA;
texture_format(#e3d_image{type=g8}) -> ?GL_LUMINANCE;
texture_format(#e3d_image{type=a8}) -> ?GL_ALPHA.
%% Long ago we tried to use compression, but we no longer do since compression
%% lowers the quality too much, especially for bump/normal maps.
internal_format(?GL_BGR) -> ?GL_RGB;
internal_format(?GL_BGRA) -> ?GL_RGBA;
internal_format(Else) -> Else.
delete(Id, #ist{images=Images0}=S) ->
delete_bump(Id),
gl:deleteTextures(1, [erase(Id)]),
Images = gb_trees:delete(Id, Images0),
S#ist{images=Images}.
delete_older(Id, #ist{images=Images0}=S) ->
Images1 = delete_older_1(gb_trees:to_list(Images0), Id),
Images = gb_trees:from_orddict(Images1),
S#ist{images=Images}.
delete_older_1([{Id,_}|T], Limit) when Id < Limit ->
delete_bump(Id),
gl:deleteTextures(1, [erase(Id)]),
delete_older_1(T, Limit);
delete_older_1(Images, _) -> Images.
delete_from(Id, #ist{images=Images0}=S) ->
Images1 = delete_from_1(gb_trees:to_list(Images0), Id, []),
Images = gb_trees:from_orddict(Images1),
S#ist{images=Images}.
delete_from_1([{Id,_}=Im|T], Limit, Acc) when Id < Limit ->
delete_from_1(T, Limit, [Im|Acc]);
delete_from_1([{Id,_}|T], Limit, Acc) ->
delete_bump(Id),
gl:deleteTextures(1, [erase(Id)]),
delete_from_1(T, Limit, Acc);
delete_from_1([], _, Acc) -> reverse(Acc).
delete_bump(Id) ->
TxId = get(Id),
case erase({Id,bump}) of
undefined -> ok;
TxId -> ok;
Bid -> gl:deleteTextures(1, [Bid])
end.
do_update(Id, In = #e3d_image{width=W,height=H,type=Type,name=NewName},
#ist{images=Images0}=S) ->
Im0 = #e3d_image{filename=File,name=OldName} = gb_trees:get(Id, Images0),
Name = if is_list(NewName), length(NewName) > 2 -> NewName;
true -> OldName
end,
Im = maybe_convert(In#e3d_image{filename=File, name=Name}),
TxId = get(Id),
Images = gb_trees:update(Id, Im, Images0),
Size = {Im0#e3d_image.width, Im0#e3d_image.height, Im0#e3d_image.type},
case Size of
{W,H,Type} ->
gl:bindTexture(?GL_TEXTURE_2D, TxId),
gl:texSubImage2D(?GL_TEXTURE_2D, 0, 0, 0,
W, H, texture_format(Im),
?GL_UNSIGNED_BYTE, Im#e3d_image.image);
_ ->
init_texture(Im, TxId)
end,
case get({Id,bump}) of
undefined ->
S#ist{images=Images};
Bid ->
create_bump(Id, Bid, S#ist{images=Images}),
S#ist{images=Images}
end.
make_unique(Name, Images0) ->
Images = [N || #e3d_image{name=N} <- gb_trees:values(Images0)],
wings_util:unique_name(Name, Images).
%%%
%%% Window for image.
%%%
window(Id) ->
Name = {image,Id},
case wings_wm:is_window(Name) of
true ->
wings_wm:raise(Name);
false ->
{Size,Title} = window_params(Id),
Pos = {10,50,highest},
Op = {seq,push,window_fun(Id)},
Props = window_props(),
wings_wm:toplevel(Name, Title, Pos, Size,
[resizable,closable,{properties,Props}], Op),
wings_wm:send(Name, {action,{viewer,100}})
end.
window_params(Id) ->
#e3d_image{width=W0,height=H0,name=Name,bytes_pp=BytesPerPixel} = info(Id),
Title = flatten(io_lib:format(?__(1,"Image: ~s [~wx~wx~w]"),
[Name,W0,H0,8*BytesPerPixel])),
{DeskW,DeskH} = wings_wm:win_size(desktop),
W = if
W0 < 250 -> 250;
W0+50 < DeskW -> W0+2;
true -> DeskW - 50
end,
H = if
H0+70 < DeskH -> H0+2;
true -> DeskH - 70
end,
{{W,H},Title}.
window_props() ->
View = #view{origin={0.0,0.0,0.0},
distance=0.65,
azimuth=0.0,
elevation=0.0,
pan_x=0.0,
pan_y=0.0,
fov=90.0,
hither=0.001,
yon=100.0},
[{current_view,View},
{orthogonal_view,true},
{allow_rotation,false},
{hide_sel_in_camera_moves,false}].
window_fun(Id) ->
fun(Ev) ->
event(Ev, Id)
end.
event(redraw, Id) ->
redraw(Id),
keep;
event(close, _) ->
delete;
event(got_focus, _) ->
Msg2 = wings_camera:help(),
Msg3 = wings_msg:button_format([], [],?__(1,"Show menu")),
Message = wings_msg:join([Msg2,Msg3]),
wings_wm:message(Message),
keep;
event({action,{viewer,Cmd}}, Id) ->
command(Cmd, Id);
event(Ev, Id) ->
case wings_camera:event(Ev, #st{shapes=gb_trees:empty()},fun() -> redraw(Id) end) of
next -> event_1(Ev, Id);
Other -> Other
end.
event_1(Ev, _) ->
case wings_menu:is_popup_event(Ev) of
no -> keep;
{yes,X,Y,_} ->
Menu = [{"12%",12},
{"25%",25},
{"50%",50},
separator,
{"100%",100,?__(1,"Show in natural size")},
separator,
{"200%",200},
{"400%",400},
{"800%",800}
],
wings_menu:popup_menu(X, Y, viewer, Menu)
end.
command(Percent, Id) when is_integer(Percent) ->
View = wings_view:current(),
#e3d_image{width=Iw,height=Ih} = info(Id),
{_,H} = wings_wm:win_size(),
Dist = 100/2*H/Ih/Percent,
PanX = -(Iw/Ih/2),
PanY = -0.5,
wings_view:set_current(View#view{distance=Dist,pan_x=PanX,pan_y=PanY}),
wings_wm:dirty(),
keep.
redraw(Id) ->
case info(Id) of
none ->
wings_wm:later(close),
keep;
Im -> redraw_1(Id, Im)
end.
redraw_1(Id, #e3d_image{width=Iw,height=Ih}) ->
gl:pushAttrib(?GL_ALL_ATTRIB_BITS),
wings_wm:clear_background(),
wings_view:load_matrices(false),
gl:enable(?GL_TEXTURE_2D),
gl:texEnvi(?GL_TEXTURE_ENV, ?GL_TEXTURE_ENV_MODE, ?GL_REPLACE),
gl:disable(?GL_DEPTH_TEST),
draw_background(0, 0, Iw/Ih, 1),
gl:enable(?GL_BLEND),
gl:blendFunc(?GL_SRC_ALPHA, ?GL_ONE_MINUS_SRC_ALPHA),
draw_image(Iw/Ih, 1, txid(Id)),
gl:bindTexture(?GL_TEXTURE_2D, 0),
%% Draw window border.
wings_io:ortho_setup(),
gl:polygonMode(?GL_FRONT_AND_BACK, ?GL_LINE),
{W,H} = wings_wm:win_size(),
gl:rectf(0.5, 0.5, W-0.5, H-0.5),
#view{distance=Dist} = wings_view:current(),
gl:popAttrib(),
%% Info line.
Percent = 100/2*H/Ih/Dist,
wings_io:info(io_lib:format("~.2f%", [Percent])).
draw_background(X, Y, W, H) ->
{Wwin,Hwin} = wings_wm:win_size(),
Ua = 0,
Va = 0,
Ub = Wwin div 16,
Vb = Hwin div 16,
gl:bindTexture(?GL_TEXTURE_2D, txid(background)),
gl:'begin'(?GL_QUADS),
gl:texCoord2f(Ua, Va),
gl:vertex2f(X, Y),
gl:texCoord2f(Ua, Vb),
gl:vertex2f(X, Y+H),
gl:texCoord2f(Ub, Vb),
gl:vertex2f(X+W, Y+H),
gl:texCoord2f(Ub, Va),
gl:vertex2f(X+W, Y),
gl:'end'().
draw_image(W, H, TxId) ->
Ua = 0, Ub = 1,
Va = 0, Vb = 1,
gl:bindTexture(?GL_TEXTURE_2D, TxId),
gl:'begin'(?GL_QUADS),
gl:texCoord2i(Ua, Va),
gl:vertex2f(0, 0),
gl:texCoord2i(Ua, Vb),
gl:vertex2f(0, H),
gl:texCoord2i(Ub, Vb),
gl:vertex2f(W, H),
gl:texCoord2i(Ub, Va),
gl:vertex2f(W, 0),
gl:'end'().
draw_image(X, Y, W, H, TxId) ->
Ua = 0, Ub = 1,
Va = 1, Vb = 0,
gl:bindTexture(?GL_TEXTURE_2D, TxId),
gl:'begin'(?GL_QUADS),
gl:texCoord2i(Ua, Va),
gl:vertex2i(X, Y),
gl:texCoord2i(Ua, Vb),
gl:vertex2i(X, Y+H),
gl:texCoord2i(Ub, Vb),
gl:vertex2i(X+W, Y+H),
gl:texCoord2i(Ub, Va),
gl:vertex2i(X+W, Y),
gl:'end'().
%%%
%%% Creating images with pre-defined patterns.
%%%
create_image() ->
Qs = [{?__(1,"Width"),256,[{range,{8,1024}}]},
{?__(2,"Height"),256,[{range,{8,1024}}]},
{?__(3,"Pattern"),
{menu,[{?__(4,"Grid"),grid},
{?__(5,"Checkerboard"),checkerboard},
{?__(6,"Vertical Bars"),vbars},
{?__(7,"Horizontal Bars"),hbars},
{?__(8,"White"),white},
{?__(9,"Black"),black}],
grid}}],
wings_ask:ask(?__(10,"Create Image"), Qs,
fun([W,H,Pattern]) ->
create_image_1(Pattern, W, H),
ignore
end).
create_image_1(Pattern, W, H) ->
Pixels = pattern(Pattern, W, H),
case {byte_size(Pixels),3*W*H} of
{S,S} -> %Assertion.
Im = #e3d_image{width=W,height=H,image=Pixels,order=upper_left},
new(atom_to_list(Pattern), Im)
end.
pattern(grid, W, H) ->
grid(W, H);
pattern(checkerboard, W, H) ->
checkerboard(W, H);
pattern(vbars, W, H) ->
vertical_bars(W, H);
pattern(hbars, W, H) ->
horizontal_bars(W, H);
pattern(white, W, H) ->
all_white(W, H);
pattern(black, W, H) ->
all_black(W, H).
%% Generate a grid image.
grid(Width, Height) ->
White = [255,255,255],
Black = [0,0,0],
WhiteRow = pattern_repeat(Width, White),
BlackLine = pattern_repeat(14, Black),
Mixed0 = pattern_repeat(14*((Width+15) div 16), [White,BlackLine|White]),
Mixed = truncate(Mixed0, 3*Width),
MixedRows = pattern_repeat(14, Mixed),
R = [WhiteRow,MixedRows|WhiteRow],
All = pattern_repeat((Height+15) div 16, R),
truncate(All, 3*Width*Height).
%% Generate a checkerboard image of 4x4 squares
%% with given side length in pixels.
checkerboard(Width, Height) ->
FourWhite = pattern_repeat(3*4, 255),
FourBlack = pattern_repeat(3*4, 0),
RoundedW = (Width+7) div 8,
RowSize = 3*Width,
R1 = truncate(pattern_repeat(RoundedW, [FourBlack|FourWhite]), RowSize),
R2 = truncate(pattern_repeat(RoundedW, [FourWhite|FourBlack]), RowSize),
R8 = [pattern_repeat(4, [R1])|pattern_repeat(4, [R2])],
truncate(pattern_repeat(RoundedW, R8), 3*Width*Height).
%% Generate a vertical bars image of 4 pixels width of given size.
vertical_bars(Width, Height) ->
W4 = pattern_repeat(3*4, 255),
B4 = pattern_repeat(3*4, 0),
Row = truncate(pattern_repeat((Width+7) div 8, [B4|W4]), 3*Width),
list_to_binary(pattern_repeat(Height, Row)).
%% Generate a horizontal bars image of 4 pixels height.
horizontal_bars(Width, Height) ->
WhiteRow = pattern_repeat(3*Width, 255),
BlackRow = pattern_repeat(3*Width, 0),
WR4 = pattern_repeat(4, WhiteRow),
BR4 = pattern_repeat(4, BlackRow),
truncate(pattern_repeat((Height+7) div 8, [BR4|WR4]), 3*Width*Height).
%% Generate an all white image with given size.
all_white(Width, Height) ->
solid(Width, Height, 255).
%% Generate an all white image with given size.
all_black(Width, Height) ->
solid(Width, Height, 0).
solid(Width, Height, Channel) ->
list_to_binary(pattern_repeat(3*Width*Height, Channel)).
pattern_repeat(0, _) -> [];
pattern_repeat(1, D) -> [D];
pattern_repeat(N, D) ->
B = pattern_repeat(N div 2, D),
case N rem 2 of
0 -> [B|B];
1 -> [D,B|B]
end.
truncate(B0, Sz) ->
<<B:Sz/binary,_/binary>> = list_to_binary(B0),
B.
%% Creating Normal-Cubemap
%% Initialize a cube map texture object that generates RGB values
%% that when expanded to a [-1,1] range in the texture-unit
%% form a normalized vector matching the per-pixel vector used to
%% access the cube map.
make_normalize_vector_cubemap(Size) ->
gl:texParameteri(?GL_TEXTURE_CUBE_MAP, ?GL_TEXTURE_WRAP_S, ?GL_CLAMP_TO_EDGE),
gl:texParameteri(?GL_TEXTURE_CUBE_MAP, ?GL_TEXTURE_WRAP_T, ?GL_CLAMP_TO_EDGE),
gl:texParameteri(?GL_TEXTURE_CUBE_MAP, ?GL_TEXTURE_WRAP_R, ?GL_CLAMP_TO_EDGE),
gl:texParameteri(?GL_TEXTURE_CUBE_MAP, ?GL_TEXTURE_MAG_FILTER, ?GL_LINEAR),
gl:texParameteri(?GL_TEXTURE_CUBE_MAP, ?GL_TEXTURE_MIN_FILTER, ?GL_LINEAR),
Sides = [?GL_TEXTURE_CUBE_MAP_POSITIVE_X, ?GL_TEXTURE_CUBE_MAP_NEGATIVE_X,
?GL_TEXTURE_CUBE_MAP_POSITIVE_Y, ?GL_TEXTURE_CUBE_MAP_NEGATIVE_Y,
?GL_TEXTURE_CUBE_MAP_POSITIVE_Z, ?GL_TEXTURE_CUBE_MAP_NEGATIVE_Z],
lists:foreach(fun(Side) -> make_cube_map(Side,Size-1,Size-1,Size, []) end, Sides).
make_cube_map(Side,-1,-1,Size,Acc) ->
Image = list_to_binary(Acc),
gl:texImage2D(Side, 0, ?GL_RGB8, Size, Size, 0,
?GL_RGB, ?GL_UNSIGNED_BYTE, Image);
make_cube_map(Side,-1,Y,Size,Acc) ->
make_cube_map(Side,Size-1,Y-1,Size,Acc);
make_cube_map(Side,X,Y,Size,Acc) ->
Vec = get_cube_vec(Side,Size,X,Y),
make_cube_map(Side,X-1,Y,Size,[Vec|Acc]).
%% Given a cube map face index, cube map size, and integer 2D face position,
%% return the cooresponding normalized vector.
get_cube_vec(Side, Size, X, Y) ->
S = (X + 0.5) / Size,
T = (Y + 0.5) / Size,
SC = S*2.0 - 1.0,
TC = T*2.0 - 1.0,
Vec =
case Side of
?GL_TEXTURE_CUBE_MAP_POSITIVE_X -> {1.0, -TC, -SC};
?GL_TEXTURE_CUBE_MAP_NEGATIVE_X -> {-1.0, -TC, SC};
?GL_TEXTURE_CUBE_MAP_POSITIVE_Y -> {SC, 1.0, TC};
?GL_TEXTURE_CUBE_MAP_NEGATIVE_Y -> {SC, -1.0, -TC};
?GL_TEXTURE_CUBE_MAP_POSITIVE_Z -> {SC, -TC, 1.0};
?GL_TEXTURE_CUBE_MAP_NEGATIVE_Z -> {-SC, -TC,-1.0}
end,
{RX,RY,RZ} = e3d_vec:norm(Vec),
[round(128+127*RX),round(128+127*RY),round(128+127*RZ)].
%% Run a computation in a worker process with a generous heap size
%% and the default generational garbage collector. Before R12B,
%% heap fragments would allow the heap to be over-committed, but in
%% R12B there will be garbage collection as soon as the heap space
%% is exhausted.
worker_process(WorkFun) ->
ResultRef = make_ref(),
{Pid,Ref} = spawn_opt(fun() -> exit({ResultRef,WorkFun()}) end,
[monitor,{min_heap_size,20000}]),
receive
{'DOWN',Ref,process,Pid,{ResultRef,Res}} ->
Res;
{'DOWN',Ref,process,Pid,Reason} ->
exit(Reason)
end.
Jump to Line
Something went wrong with that request. Please try again.