aboutsummaryrefslogblamecommitdiffstats
path: root/lib/percept/src/percept_image.erl
blob: 5baedabecfbcd8cdf5fa8dba407f5d3c64269a4f (plain) (tree)


























































































































































































































































































































                                                                                                                                          
%% 
%% %CopyrightBegin%
%% 
%% Copyright Ericsson AB 2007-2009. All Rights Reserved.
%% 
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%% 
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%% 
%% %CopyrightEnd%

-module(percept_image).
-export([	proc_lifetime/5,
		percentage/3,
		graph/3, 
		graph/4, 
		activities/3, 
		activities/4]).
-record(graph_area, {x = 0, y = 0, width, height}).
-compile(inline).

%%% -------------------------------------
%%% GRAF
%%% -------------------------------------

%% graph(Widht, Height, Range, Data)

graph(Width, Height, {RXmin, RYmin, RXmax, RYmax}, Data) ->
    Data2 = [{X, Y1 + Y2} || {X, Y1, Y2} <- Data],
    MinMax = percept_analyzer:minmax(Data2),
    {Xmin, Ymin, Xmax, Ymax} = MinMax, 
    graf1(Width, Height,{	lists:min([RXmin, Xmin]), 
    				lists:min([RYmin, Ymin]),
    				lists:max([RXmax, Xmax]), 
				lists:max([RYmax, Ymax])}, Data).

%% graph(Widht, Height, Data) = Image
%% In:
%%	Width = integer(),
%%	Height = integer(),
%%	Data = [{Time, Procs, Ports}]
%%	Time = float()
%%	Procs = integer()
%%	Ports = integer()
%% Out:
%%	Image = binary()

graph(Width, Height, Data) ->
    Data2 = [{X, Y1 + Y2} || {X, Y1, Y2} <- Data],
    Bounds = percept_analyzer:minmax(Data2),
    graf1(Width, Height, Bounds, Data).

graf1(Width, Height, {Xmin, Ymin, Xmax, Ymax}, Data) ->
    % Calculate areas
    HO = 20,
    GrafArea   = #graph_area{x = HO, y = 4, width = Width - 2*HO, height = Height - 17},
    XticksArea = #graph_area{x = HO, y = Height - 13, width = Width - 2*HO, height = 13},
    YticksArea = #graph_area{x = 1,  y = 4, width = HO, height = Height - 17},
    
    %% Initiate Image

    Image = egd:create(Width, Height),
 
    %% Set colors
    
    Black = egd:color(Image, {0, 0, 0}),
    ProcColor = egd:color(Image, {0, 255, 0}),
    PortColor = egd:color(Image, {255, 0, 0}),
    
    %% Draw graf, xticks and yticks
    draw_graf(Image, Data, {Black, ProcColor, PortColor}, GrafArea, {Xmin, Ymin, Xmax, Ymax}),
    draw_xticks(Image, Black, XticksArea, {Xmin, Xmax}, Data),
    draw_yticks(Image, Black, YticksArea, {Ymin, Ymax}),
    
    %% Kill image and return binaries
    Binary = egd:render(Image, png),
    egd:destroy(Image),
    Binary.

%% draw_graf(Image, Data, Color, GraphArea, DataBounds)
%% Image, port to Image
%% Data, list of three tuple data, (X, Y1, Y2)
%% Color, {ForegroundColor, ProcFillColor, PortFillColor}
%% DataBounds, {Xmin, Ymin, Xmax, Ymax}

draw_graf(Im, Data, Colors, GA = #graph_area{x = X0, y = Y0, width = Width, height = Height}, {Xmin, _Ymin, Xmax, Ymax}) ->
    Dx = (Width)/(Xmax - Xmin),
    Dy = (Height)/(Ymax),
    Plotdata = [{trunc(X0 + X*Dx - Xmin*Dx), trunc(Y0 + Height - Y1*Dy), trunc(Y0 + Height - (Y1 + Y2)*Dy)} || {X, Y1, Y2} <- Data],
    draw_graf(Im, Plotdata, Colors, GA).

draw_graf(Im, [{X1, Yproc1, Yport1}, {X2, Yproc2, Yport2}|Data], C, GA) when X2 - X1 < 1 ->
    draw_graf(Im, [{X1, [{Yproc2, Yport2},{Yproc1, Yport1}]}|Data], C, GA);

draw_graf(Im, [{X1, Ys1}, {X2, Yproc2, Yport2}|Data], C, GA) when X2 - X1 < 1, is_list(Ys1) ->
    draw_graf(Im, [{X1, [{Yproc2, Yport2}|Ys1]}|Data], C, GA);

draw_graf(Im, [{X1, Yproc1, Yport1}, {X2, Yproc2, Yport2}|Data], C = {B, PrC, PoC}, GA = #graph_area{y = Y0, height = H})  ->
    GyZero  = trunc(Y0 + H),
    egd:filledRectangle(Im, {X1, GyZero}, {X2, Yproc1}, PrC),
    egd:filledRectangle(Im, {X1, Yproc1}, {X2, Yport1}, PoC),
    egd:line(Im, {X1, Yport1}, {X2, Yport1}, B), % top line
    egd:line(Im, {X1, Yport2}, {X1, Yport1}, B), % right line
    egd:line(Im, {X2, Yport1}, {X2, Yport2}, B), % right line
    draw_graf(Im, [{X2, Yproc2, Yport2}|Data], C, GA);

draw_graf(Im, [{X1, Ys1 = [{Yproc1,Yport1}|_]}, {X2, Yproc2, Yport2}|Data], C = {B, PrC, PoC}, GA = #graph_area{y = Y0, height = H})  ->
    GyZero  = trunc(Y0 + H),
    Yprocs = [Yp || {Yp, _} <- Ys1],
    Yports = [Yp || {_, Yp} <- Ys1],

    YprMin = lists:min(Yprocs),
    YprMax = lists:max(Yprocs),
    YpoMax = lists:max(Yports),
    egd:filledRectangle(Im, {X1, GyZero}, {X2, Yproc1}, PrC),
    egd:filledRectangle(Im, {X1, Yproc1}, {X2, Yport1}, PoC),
    egd:filledRectangle(Im, {X1, Yport1}, {X2, Yport1}, B), % top line
    egd:filledRectangle(Im, {X2, Yport1}, {X2, Yport2}, B), % right line

    egd:filledRectangle(Im, {X1, GyZero}, {X1, YprMin}, PrC), % left proc green line
    egd:filledRectangle(Im, {X1, YprMax}, {X1, YpoMax}, PoC), % left port line
    egd:filledRectangle(Im, {X1, YprMax}, {X1, YprMin}, B),
     
    draw_graf(Im, [{X2, Yproc2, Yport2}|Data], C, GA);
draw_graf(_, _, _, _) -> ok.

draw_xticks(Image, Color, XticksArea, {Xmin, Xmax}, Data) ->
    #graph_area{x = X0, y = Y0, width = Width} = XticksArea,
    
    DX = Width/(Xmax - Xmin),
    Offset = X0 - Xmin*DX, 
    Y = trunc(Y0),
    Font = load_font(),
    {FontW, _FontH} = egd_font:size(Font),
    egd:filledRectangle(Image, {trunc(X0), Y}, {trunc(X0 + Width), Y}, Color), 
    lists:foldl(
    	fun ({X,_,_}, PX) ->
	    X1 = trunc(Offset + X*DX),
	    
	    % Optimization:
	    % if offset has past half the previous text
	    % start checking this text
	    
	    if 
	    	X1 > PX ->
		    Text = lists:flatten(io_lib:format("~.3f", [float(X)])),
		    TextLength = length(Text),
		    TextWidth = TextLength*FontW,
		    Spacing = 2,
		    if 
		    	X1 > PX + round(TextWidth/2) + Spacing ->
		    	    egd:line(Image, {X1, Y - 3}, {X1, Y + 3}, Color),
		    	    text(Image, {X1 - round(TextWidth/2), Y + 2}, Font, Text, Color),
			    X1 + round(TextWidth/2) + Spacing;
			true ->
			    PX
		    end;
		true ->
		    PX
	    end
	end, 0, Data).

draw_yticks(Im, Color, TickArea, {_,Ymax}) ->
    #graph_area{x = X0, y = Y0, width = Width, height = Height} = TickArea,
    Font = load_font(),
    X = trunc(X0 + Width),
    Dy = (Height)/(Ymax),
    Yts = if 
	Height/(Ymax*12) < 1.0 -> round(1 + Ymax*15/Height);
	true -> 1
    end,
    egd:filledRectangle(Im, {X, trunc(0 + Y0)}, {X, trunc(Y0 + Height)}, Color),
    draw_yticks0(Im, Font, Color, 0, Yts, Ymax, {X, Height, Dy}).

draw_yticks0(Im, Font, Color, Yi, Yts, Ymax, Area) when Yi < Ymax -> 
    {X, Height, Dy} = Area, 
    Y = round(Height - (Yi*Dy) + 3),

    egd:filledRectangle(Im, {X - 3, Y}, {X + 3, Y}, Color), 
    Text = lists:flatten(io_lib:format("~p", [Yi])),
    text(Im, {0, Y - 4}, Font, Text, Color),
    draw_yticks0(Im, Font, Color, Yi + Yts, Yts, Ymax, Area);
draw_yticks0(_, _, _, _, _, _, _) -> ok.

%%% -------------------------------------
%%% ACTIVITIES
%%% -------------------------------------

%% activities(Width, Height, Range, Activities) -> Binary
%% In:
%%	Width = integer()
%%	Height = integer()
%%	Range = {float(), float()}
%%	Activities = [{float(), active | inactive}]
%% Out:
%%	Binary = binary()

activities(Width, Height, {UXmin, UXmax}, Activities) ->
    Xs = [ X || {X,_} <- Activities],
    Xmin = lists:min(Xs),
    Xmax = lists:max(Xs),
    activities0(Width, Height, {lists:min([Xmin, UXmin]), lists:max([UXmax, Xmax])}, Activities).

activities(Width, Height, Activities) ->
    Xs = [ X || {X,_} <- Activities],
    Xmin = lists:min(Xs),
    Xmax = lists:max(Xs),
    activities0(Width, Height, {Xmin, Xmax}, Activities).

activities0(Width, Height, {Xmin, Xmax}, Activities) ->
    Image = egd:create(Width, Height),
    Grey = egd:color(Image, {200, 200, 200}),
    HO = 20,
    ActivityArea = #graph_area{x = HO, y = 0, width = Width - 2*HO, height = Height},
    egd:filledRectangle(Image, {0, 0}, {Width, Height}, Grey),
    draw_activity(Image, {Xmin, Xmax}, ActivityArea, Activities),
    Binary = egd:render(Image, png),
    egd:destroy(Image),
    Binary.

draw_activity(Image, {Xmin, Xmax}, Area = #graph_area{ width = Width }, Acts) ->
    White = egd:color({255, 255, 255}),
    Green = egd:color({0,250, 0}),
    Black = egd:color({0, 0, 0}),

    Dx    = Width/(Xmax - Xmin),

    draw_activity(Image, {Xmin, Xmax}, Area, {White, Green, Black}, Dx, Acts).

draw_activity(_, _, _, _, _, [_]) -> ok;
draw_activity(Image, {Xmin, Xmax}, Area = #graph_area{ height = Height, x = X0 }, {Cw, Cg, Cb}, Dx, [{Xa1, State}, {Xa2, Act2} | Acts]) ->
    X1 = erlang:trunc(X0 + Dx*Xa1 - Xmin*Dx),
    X2 = erlang:trunc(X0 + Dx*Xa2 - Xmin*Dx),

    case State of
	inactive ->
	    egd:filledRectangle(Image, {X1, 0}, {X2, Height - 1}, Cw),
	    egd:rectangle(Image, {X1, 0}, {X2, Height - 1}, Cb);
	active ->
	    egd:filledRectangle(Image, {X1, 0}, {X2, Height - 1}, Cg),
	    egd:rectangle(Image, {X1, 0}, {X2, Height - 1}, Cb)
    end,
    draw_activity(Image, {Xmin, Xmax}, Area, {Cw, Cg, Cb}, Dx, [{Xa2, Act2} | Acts]).

 

%%% -------------------------------------
%%% Process lifetime
%%% Used by processes page
%%% -------------------------------------

proc_lifetime(Width, Height, Start, End, ProfileTime) ->
    Im = egd:create(round(Width), round(Height)),
    Black = egd:color(Im, {0, 0, 0}),
    Green = egd:color(Im, {0, 255, 0}),

    % Ratio and coordinates

    DX = (Width-1)/ProfileTime,
    X1 = round(DX*Start),
    X2 = round(DX*End),

    % Paint
    egd:filledRectangle(Im, {X1, 0}, {X2, Height - 1}, Green),
    egd:rectangle(Im, {X1, 0}, {X2, Height - 1}, Black),

    Binary = egd:render(Im, png),
    egd:destroy(Im),
    Binary.

%%% -------------------------------------
%%% Percentage
%%% Used by process_info page
%%% Percentage should be 0.0 -> 1.0
%%% -------------------------------------
percentage(Width, Height, Percentage) ->
    Im = egd:create(round(Width), round(Height)),
    Font = load_font(),
    Black = egd:color(Im, {0, 0, 0}),
    Green = egd:color(Im, {0, 255, 0}),

    % Ratio and coordinates

    X = round(Width - 1 - Percentage*(Width - 1)),

    % Paint
    egd:filledRectangle(Im, {X, 0}, {Width - 1, Height - 1}, Green),
    {FontW, _} = egd_font:size(Font), 
    String = lists:flatten(io_lib:format("~.10B %", [round(100*Percentage)])),

    text(	Im, 
		{round(Width/2 - (FontW*length(String)/2)), 0}, 
    		Font,
		String,
		Black),
    egd:rectangle(Im, {X, 0}, {Width - 1, Height - 1}, Black),
    
    Binary = egd:render(Im, png),
    egd:destroy(Im),
    Binary.


load_font() ->
    Filename = filename:join([code:priv_dir(percept),"fonts", "6x11_latin1.wingsfont"]),
    egd_font:load(Filename).
    
text(Image, {X,Y}, Font, Text, Color) ->
    egd:text(Image, {X,Y-2}, Font, Text, Color).