%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2008-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% %% %% @doc egd_primitives %% -module(egd_primitives). -export([create/2, color/1, pixel/3, polygon/3, line/4, line/5, arc/4, arc/5, rectangle/4, filledRectangle/4, filledEllipse/4, filledTriangle/5, text/5]). -export([info/1, object_info/1, rgb_float2byte/1]). -export([arc_to_edges/3, convex_hull/1, edges/1]). -include("egd.hrl"). %% API info info(I) -> W = I#image.width, H = I#image.height, io:format("Dimensions: ~p x ~p~n", [W,H]), io:format("Number of image objects: ~p~n", [length(I#image.objects)]), TotalPoints = info_objects(I#image.objects,0), io:format("Total points: ~p [~p %]~n", [TotalPoints, 100*TotalPoints/(W*H)]), ok. info_objects([],N) -> N; info_objects([O | Os],N) -> Points = length(O#image_object.points), info_objects(Os,N+Points). object_info(O) -> io:format("Object information: ~p~n", [O#image_object.type]), io:format("- Number of points: ~p~n", [length(O#image_object.points)]), io:format("- Bounding box: ~p~n", [O#image_object.span]), io:format("- Color: ~p~n", [O#image_object.color]), ok. %% interface functions line(#image{objects=Os}=I, Sp, Ep, Color) -> line(#image{objects=Os}=I, Sp, Ep, 1, Color). line(#image{objects=Os}=I, Sp, Ep, Wd, Color) -> I#image{objects=[#image_object{ internals = Wd, type = line, points = [Sp, Ep], span = span([Sp, Ep]), color = Color}|Os]}. arc(I, {Sx,Sy} = Sp, {Ex,Ey} = Ep, Color) -> X = Ex - Sx, Y = Ey - Sy, R = math:sqrt(X*X + Y*Y)/2, arc(I, Sp, Ep, R, Color). arc(#image{objects=Os}=I, Sp, Ep, D, Color) -> SpanPts = lists:flatten([ [{X + D, Y + D}, {X + D, Y - D}, {X - D, Y + D}, {X - D, Y - D}] || {X,Y} <- [Sp,Ep]]), I#image{objects=[#image_object{ internals = D, type = arc, points = [Sp, Ep], span = span(SpanPts), color = Color}|Os]}. pixel(#image{objects=Os}=I, Point, Color) -> I#image{objects=[#image_object{ type = pixel, points = [Point], span = span([Point]), color = Color}|Os]}. rectangle(#image{objects=Os}=I, Sp, Ep, Color) -> I#image{objects=[#image_object{ type = rectangle, points = [Sp, Ep], span = span([Sp, Ep]), color = Color}|Os]}. filledRectangle(#image{objects=Os}=I, Sp, Ep, Color) -> I#image{objects=[#image_object{ type = filled_rectangle, points = [Sp, Ep], span = span([Sp, Ep]), color = Color}|Os]}. filledEllipse(#image{objects=Os}=I, Sp, Ep, Color) -> {X0,Y0,X1,Y1} = Span = span([Sp, Ep]), Xr = (X1 - X0)/2, Yr = (Y1 - Y0)/2, Xp = - X0 - Xr, Yp = - Y0 - Yr, I#image{objects=[#image_object{ internals = {Xp,Yp, Xr*Xr,Yr*Yr}, type = filled_ellipse, points = [Sp, Ep], span = Span, color = Color}|Os]}. filledTriangle(#image{objects=Os}=I, P1, P2, P3, Color) -> I#image{objects=[#image_object{ type = filled_triangle, points = [P1,P2,P3], span = span([P1,P2,P3]), color = Color}|Os]}. polygon(#image{objects=Os}=I, Points, Color) -> I#image{objects=[#image_object{ type = polygon, points = Points, span = span(Points), color = Color}|Os]}. text(#image{objects=Os}=I, {Xs,Ys}=Sp, Font, Text, Color) -> {FW,FH} = egd_font:size(Font), Length = length(Text), Ep = {Xs + Length*FW, Ys + FH + 5}, I#image{objects=[#image_object{ internals = {Font, Text}, type = text_horizontal, points = [Sp], span = span([Sp,Ep]), color = Color}|Os]}. create(W, H) -> #image{width = W, height = H}. color(Color) when is_atom(Color) -> rgba_byte2float(name_to_color(Color, 255)); color({Color, A}) when is_atom(Color) -> rgba_byte2float(name_to_color(Color, A)); color({R,G,B}) -> rgba_byte2float({R,G,B, 255}); color(C) -> rgba_byte2float(C). name_to_color(Color, A) -> case Color of %% HTML default colors black -> { 0, 0, 0, A}; silver -> {192, 192, 192, A}; gray -> {128, 128, 128, A}; white -> {128, 0, 0, A}; maroon -> {255, 0, 0, A}; red -> {128, 0, 128, A}; purple -> {128, 0, 128, A}; fuchia -> {255, 0, 255, A}; green -> { 0, 128, 0, A}; lime -> { 0, 255, 0, A}; olive -> {128, 128, 0, A}; yellow -> {255, 255, 0, A}; navy -> { 0, 0, 128, A}; blue -> { 0, 0, 255, A}; teal -> { 0, 128, 0, A}; aqua -> { 0, 255, 155, A}; %% HTML color extensions steelblue -> { 70, 130, 180, A}; royalblue -> { 4, 22, 144, A}; cornflowerblue -> {100, 149, 237, A}; lightsteelblue -> {176, 196, 222, A}; mediumslateblue -> {123, 104, 238, A}; slateblue -> {106, 90, 205, A}; darkslateblue -> { 72, 61, 139, A}; midnightblue -> { 25, 25, 112, A}; darkblue -> { 0, 0, 139, A}; mediumblue -> { 0, 0, 205, A}; dodgerblue -> { 30, 144, 255, A}; deepskyblue -> { 0, 191, 255, A}; lightskyblue -> {135, 206, 250, A}; skyblue -> {135, 206, 235, A}; lightblue -> {173, 216, 230, A}; powderblue -> {176, 224, 230, A}; azure -> {240, 255, 255, A}; lightcyan -> {224, 255, 255, A}; paleturquoise -> {175, 238, 238, A}; mediumturquoise -> { 72, 209, 204, A}; lightseagreen -> { 32, 178, 170, A}; darkcyan -> { 0, 139, 139, A}; cadetblue -> { 95, 158, 160, A}; darkturquoise -> { 0, 206, 209, A}; cyan -> { 0, 255, 255, A}; turquoise -> { 64, 224, 208, A}; aquamarine -> {127, 255, 212, A}; mediumaquamarine -> {102, 205, 170, A}; darkseagreen -> {143, 188, 143, A}; mediumseagreen -> { 60, 179, 113, A}; seagreen -> { 46, 139, 87, A}; darkgreen -> { 0, 100, 0, A}; forestgreen -> { 34, 139, 34, A}; limegreen -> { 50, 205, 50, A}; chartreuse -> {127, 255, 0, A}; lawngreen -> {124, 252, 0, A}; greenyellow -> {173, 255, 47, A}; yellowgreen -> {154, 205, 50, A}; palegreen -> {152, 251, 152, A}; lightgreen -> {144, 238, 144, A}; springgreen -> { 0, 255, 127, A}; darkolivegreen -> { 85, 107, 47, A}; olivedrab -> {107, 142, 35, A}; darkkhaki -> {189, 183, 107, A}; darkgoldenrod -> {184, 134, 11, A}; goldenrod -> {218, 165, 32, A}; gold -> {255, 215, 0, A}; khaki -> {240, 230, 140, A}; palegoldenrod -> {238, 232, 170, A}; blanchedalmond -> {255, 235, 205, A}; moccasin -> {255, 228, 181, A}; wheat -> {245, 222, 179, A}; navajowhite -> {255, 222, 173, A}; burlywood -> {222, 184, 135, A}; tan -> {210, 180, 140, A}; rosybrown -> {188, 143, 143, A}; sienna -> {160, 82, 45, A}; saddlebrown -> {139, 69, 19, A}; chocolate -> {210, 105, 30, A}; peru -> {205, 133, 63, A}; sandybrown -> {244, 164, 96, A}; darkred -> {139, 0, 0, A}; brown -> {165, 42, 42, A}; firebrick -> {178, 34, 34, A}; indianred -> {205, 92, 92, A}; lightcoral -> {240, 128, 128, A}; salmon -> {250, 128, 114, A}; darksalmon -> {233, 150, 122, A}; lightsalmon -> {255, 160, 122, A}; coral -> {255, 127, 80, A}; tomato -> {255, 99, 71, A}; darkorange -> {255, 140, 0, A}; orange -> {255, 165, 0, A}; orangered -> {255, 69, 0, A}; crimson -> {220, 20, 60, A}; deeppink -> {255, 20, 147, A}; fuchsia -> {255, 0, 255, A}; magenta -> {255, 0, 255, A}; hotpink -> {255, 105, 180, A}; lightpink -> {255, 182, 193, A}; pink -> {255, 192, 203, A}; palevioletred -> {219, 112, 147, A}; mediumvioletred -> {199, 21, 133, A}; darkmagenta -> {139, 0, 139, A}; mediumpurple -> {147, 112, 219, A}; blueviolet -> {138, 43, 226, A}; indigo -> { 75, 0, 130, A}; darkviolet -> {148, 0, 211, A}; darkorchid -> {153, 50, 204, A}; mediumorchid -> {186, 85, 211, A}; orchid -> {218, 112, 214, A}; violet -> {238, 130, 238, A}; plum -> {221, 160, 221, A}; thistle -> {216, 191, 216, A}; lavender -> {230, 230, 250, A}; ghostwhite -> {248, 248, 255, A}; aliceblue -> {240, 248, 255, A}; mintcream -> {245, 255, 250, A}; honeydew -> {240, 255, 240, A}; lemonchiffon -> {255, 250, 205, A}; cornsilk -> {255, 248, 220, A}; lightyellow -> {255, 255, 224, A}; ivory -> {255, 255, 240, A}; floralwhite -> {255, 250, 240, A}; linen -> {250, 240, 230, A}; oldlace -> {253, 245, 230, A}; antiquewhite -> {250, 235, 215, A}; bisque -> {255, 228, 196, A}; peachpuff -> {255, 218, 185, A}; papayawhip -> {255, 239, 213, A}; beige -> {245, 245, 220, A}; seashell -> {255, 245, 238, A}; lavenderblush -> {255, 240, 245, A}; mistyrose -> {255, 228, 225, A}; snow -> {255, 250, 250, A}; whitesmoke -> {245, 245, 245, A}; gainsboro -> {220, 220, 220, A}; lightgrey -> {211, 211, 211, A}; darkgray -> {169, 169, 169, A}; lightslategray -> {119, 136, 153, A}; slategray -> {112, 128, 144, A}; dimgray -> {105, 105, 105, A}; darkslategray -> { 47, 79, 79, A}; mediumspringgreen -> { 0, 250, 154, A}; lightgoldenrodyellow -> {250, 250, 210, A} end. %%% Generic transformations %% arc_to_edges %% In: %% P1 :: point(), %% P2 :: point(), %% D :: float(), %% Out: %% Res :: [edges()] arc_to_edges(P0, P1, D) when abs(D) < 0.5 -> [{P0,P1}]; arc_to_edges({X0,Y0}, {X1,Y1}, D) -> Vx = X1 - X0, Vy = Y1 - Y0, Mx = X0 + 0.5 * Vx, My = Y0 + 0.5 * Vy, % Scale V by Rs L = math:sqrt(Vx*Vx + Vy*Vy), Sx = D*Vx/L, Sy = D*Vy/L, Bx = trunc(Mx - Sy), By = trunc(My + Sx), arc_to_edges({X0,Y0}, {Bx,By}, D/4) ++ arc_to_edges({Bx,By}, {X1,Y1}, D/4). %% edges %% In: %% Pts :: [point()] %% Out: %% Edges :: [{point(),point()}] edges([]) -> []; edges([P0|_] = Pts) -> edges(Pts, P0,[]). edges([P1], P0, Out) -> [{P1,P0}|Out]; edges([P1,P2|Pts],P0,Out) -> edges([P2|Pts],P0,[{P1,P2}|Out]). %% convex_hull %% In: %% Ps :: [point()] %% Out: %% Res :: [point()] convex_hull(Ps) -> P0 = lower_right(Ps), [P1|Ps1] = lists:sort(fun (P2,P1) -> case point_side({P1,P0},P2) of left -> true; _ -> false end end, Ps -- [P0]), convex_hull(Ps1, [P1,P0]). convex_hull([], W) -> W; convex_hull([P|Pts], [P1,P2|W]) -> case point_side({P2,P1},P) of left -> convex_hull(Pts, [P,P1,P2|W]); _ -> convex_hull([P|Pts], [P2|W]) end. lower_right([P|Pts]) -> lower_right(P, Pts). lower_right(P, []) -> P; lower_right({X0,Y0}, [{_,Y}|Pts]) when Y < Y0 -> lower_right({X0,Y0}, Pts); lower_right({X0,Y0}, [{X,Y}|Pts]) when X < X0, Y < Y0 -> lower_right({X0,Y0}, Pts); lower_right(_,[P|Pts]) -> lower_right(P, Pts). point_side({{X0,Y0}, {X1, Y1}}, {X2, Y2}) -> point_side((X1 - X0)*(Y2 - Y0) - (X2 - X0)*(Y1 - Y0)). point_side(D) when D > 0 -> left; point_side(D) when D < 0 -> right; point_side(_) -> on_line. %% AUX span([{X0,Y0}|Points]) -> span(Points,X0,Y0,X0,Y0). span([{X0,Y0}|Points],Xmin,Ymin,Xmax,Ymax) -> span(Points,erlang:min(Xmin,X0), erlang:min(Ymin,Y0), erlang:max(Xmax,X0), erlang:max(Ymax,Y0)); span([],Xmin,Ymin,Xmax,Ymax) -> {Xmin,Ymin,Xmax,Ymax}. rgb_float2byte({R,G,B}) -> rgb_float2byte({R,G,B,1.0}); rgb_float2byte({R,G,B,A}) -> {trunc(R*255), trunc(G*255), trunc(B*255), trunc(A*255)}. rgba_byte2float({R,G,B,A}) -> {R/255,G/255,B/255,A/255}.