%% %% %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).