-module(sparklinekit@internal@raster). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/sparklinekit/internal/raster.gleam"). -export([new/3, to_grid/1, put/4, fill_circle/5, fill_vertical_gradient/7, draw_line/7, fill_rect/6, fill_rounded_rect/7]). -export_type([canvas/0]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. ?MODULEDOC(false). -type canvas() :: {canvas, integer(), integer(), sparklinekit@internal@color:rgba(), gleam@dict:dict(integer(), gleam@dict:dict(integer(), sparklinekit@internal@color:rgba()))}. -file("src/sparklinekit/internal/raster.gleam", 29). ?DOC(false). -spec new(integer(), integer(), sparklinekit@internal@color:rgba()) -> canvas(). new(Width, Height, Background) -> {canvas, Width, Height, Background, maps:new()}. -file("src/sparklinekit/internal/raster.gleam", 208). ?DOC(false). -spec float_sqrt(float()) -> float(). float_sqrt(Value) -> case Value =< +0.0 of true -> +0.0; false -> case gleam@float:square_root(Value) of {ok, V} -> V; {error, _} -> +0.0 end end. -file("src/sparklinekit/internal/raster.gleam", 253). ?DOC(false). -spec interpolate( sparklinekit@internal@color:rgba(), sparklinekit@internal@color:rgba(), float() ) -> sparklinekit@internal@color:rgba(). interpolate(A, B, T) -> T_ = case T of V when V < +0.0 -> +0.0; V@1 when V@1 > 1.0 -> 1.0; V@2 -> V@2 end, Mix = fun(A_ch, B_ch) -> Af = erlang:float(A_ch), Bf = erlang:float(B_ch), case erlang:round(Af + ((Bf - Af) * T_)) of V@3 when V@3 < 0 -> 0; V@4 when V@4 > 255 -> 255; V@5 -> V@5 end end, {rgba, Ar, Ag, Ab, Aa} = A, {rgba, Br, Bg, Bb, Ba} = B, {rgba, Mix(Ar, Br), Mix(Ag, Bg), Mix(Ab, Bb), Mix(Aa, Ba)}. -file("src/sparklinekit/internal/raster.gleam", 300). ?DOC(false). -spec do_range(integer(), integer(), list(integer())) -> list(integer()). do_range(Current, Lo, Acc) -> case Current < Lo of true -> Acc; false -> do_range(Current - 1, Lo, [Current | Acc]) end. -file("src/sparklinekit/internal/raster.gleam", 293). ?DOC(false). -spec range(integer(), integer()) -> list(integer()). range(Lo, Hi) -> case Lo > Hi of true -> []; false -> do_range(Hi, Lo, []) end. -file("src/sparklinekit/internal/raster.gleam", 276). ?DOC(false). -spec to_grid(canvas()) -> list(list(sparklinekit@internal@color:rgba())). to_grid(Canvas) -> Xs = range(0, erlang:element(2, Canvas) - 1), _pipe = range(0, erlang:element(3, Canvas) - 1), gleam@list:map( _pipe, fun(Y) -> case gleam_stdlib:map_get(erlang:element(5, Canvas), Y) of {error, _} -> gleam@list:map(Xs, fun(_) -> erlang:element(4, Canvas) end); {ok, Row} -> gleam@list:map( Xs, fun(X) -> case gleam_stdlib:map_get(Row, X) of {ok, C} -> C; {error, _} -> erlang:element(4, Canvas) end end ) end end ). -file("src/sparklinekit/internal/raster.gleam", 307). ?DOC(false). -spec current_at(canvas(), integer(), integer()) -> sparklinekit@internal@color:rgba(). current_at(Canvas, X, Y) -> case gleam_stdlib:map_get(erlang:element(5, Canvas), Y) of {error, _} -> erlang:element(4, Canvas); {ok, Row} -> case gleam_stdlib:map_get(Row, X) of {ok, C} -> C; {error, _} -> erlang:element(4, Canvas) end end. -file("src/sparklinekit/internal/raster.gleam", 318). ?DOC(false). -spec in_bounds(canvas(), integer(), integer()) -> boolean(). in_bounds(Canvas, X, Y) -> (((X >= 0) andalso (X < erlang:element(2, Canvas))) andalso (Y >= 0)) andalso (Y < erlang:element(3, Canvas)). -file("src/sparklinekit/internal/raster.gleam", 40). ?DOC(false). -spec put(canvas(), integer(), integer(), sparklinekit@internal@color:rgba()) -> canvas(). put(Canvas, X, Y, Colour) -> case in_bounds(Canvas, X, Y) of false -> Canvas; true -> Existing = current_at(Canvas, X, Y), Blended = sparklinekit@internal@color:over(Colour, Existing), Row = case gleam_stdlib:map_get(erlang:element(5, Canvas), Y) of {ok, R} -> R; {error, _} -> maps:new() end, Row@1 = gleam@dict:insert(Row, X, Blended), {canvas, erlang:element(2, Canvas), erlang:element(3, Canvas), erlang:element(4, Canvas), gleam@dict:insert(erlang:element(5, Canvas), Y, Row@1)} end. -file("src/sparklinekit/internal/raster.gleam", 182). ?DOC(false). -spec plot_disc_pixel( canvas(), integer(), integer(), float(), float(), float(), sparklinekit@internal@color:rgba() ) -> canvas(). plot_disc_pixel(Canvas, X, Y, Cx, Cy, Radius, Colour) -> Dx = (erlang:float(X) + 0.5) - Cx, Dy = (erlang:float(Y) + 0.5) - Cy, Dist = float_sqrt((Dx * Dx) + (Dy * Dy)), Coverage = case Dist =< (Radius - 0.5) of true -> 1.0; false -> case Dist >= (Radius + 0.5) of true -> +0.0; false -> (Radius + 0.5) - Dist end end, case Coverage > +0.0 of false -> Canvas; true -> put( Canvas, X, Y, sparklinekit@internal@color:with_alpha(Colour, Coverage) ) end. -file("src/sparklinekit/internal/raster.gleam", 157). ?DOC(false). -spec fill_circle( canvas(), float(), float(), float(), sparklinekit@internal@color:rgba() ) -> canvas(). fill_circle(Canvas, Cx, Cy, Radius, Colour) -> case Radius =< +0.0 of true -> Canvas; false -> X0 = erlang:round((Cx - Radius) - 1.0), X1 = erlang:round((Cx + Radius) + 1.0), Y0 = erlang:round((Cy - Radius) - 1.0), Y1 = erlang:round((Cy + Radius) + 1.0), Xs = range(X0, X1), _pipe = range(Y0, Y1), gleam@list:fold( _pipe, Canvas, fun(C, Y) -> gleam@list:fold( Xs, C, fun(C2, X) -> plot_disc_pixel(C2, X, Y, Cx, Cy, Radius, Colour) end ) end ) end. -file("src/sparklinekit/internal/raster.gleam", 222). ?DOC(false). -spec fill_vertical_gradient( canvas(), float(), float(), float(), float(), sparklinekit@internal@color:rgba(), sparklinekit@internal@color:rgba() ) -> canvas(). fill_vertical_gradient(Canvas, X0, Y0, X1, Y1, Top_colour, Bottom_colour) -> Xa = erlang:round(gleam@float:min(X0, X1)), Xb = erlang:round(gleam@float:max(X0, X1)), Ya = erlang:round(gleam@float:min(Y0, Y1)), Yb = erlang:round(gleam@float:max(Y0, Y1)), case (Xa >= Xb) orelse (Ya >= Yb) of true -> Canvas; false -> Xs = range(Xa, Xb - 1), Span_f = erlang:float(Yb - Ya), _pipe = range(Ya, Yb - 1), gleam@list:fold( _pipe, Canvas, fun(C, Y) -> T = case Span_f =< +0.0 of true -> +0.0; false -> case Span_f of +0.0 -> +0.0; -0.0 -> -0.0; Gleam@denominator -> erlang:float(Y - Ya) / Gleam@denominator end end, Row_colour = interpolate(Top_colour, Bottom_colour, T), gleam@list:fold( Xs, C, fun(C2, X) -> put(C2, X, Y, Row_colour) end ) end ) end. -file("src/sparklinekit/internal/raster.gleam", 400). ?DOC(false). -spec isqrt_loop(integer(), integer()) -> integer(). isqrt_loop(Value, Guess) -> Next = (Guess + (case Guess of 0 -> 0; Gleam@denominator -> Value div Gleam@denominator end)) div 2, case Next >= Guess of true -> Guess; false -> isqrt_loop(Value, Next) end. -file("src/sparklinekit/internal/raster.gleam", 393). ?DOC(false). -spec isqrt(integer()) -> integer(). isqrt(Value) -> case Value =< 0 of true -> 0; false -> isqrt_loop(Value, Value) end. -file("src/sparklinekit/internal/raster.gleam", 374). ?DOC(false). -spec row_inset(integer(), integer(), integer()) -> integer(). row_inset(Dy, Height, Radius) -> Top_zone = Dy < Radius, Bottom_zone = Dy >= (Height - Radius), case {Top_zone, Bottom_zone} of {false, false} -> 0; {_, _} -> Local_y = case Top_zone of true -> (Radius - 1) - Dy; false -> Dy - (Height - Radius) end, R_sq = Radius * Radius, Local_y_sq = Local_y * Local_y, Max_x_sq = R_sq - Local_y_sq, Dx = isqrt(Max_x_sq), Radius - Dx end. -file("src/sparklinekit/internal/raster.gleam", 475). ?DOC(false). -spec pixel_coverage(float(), float(), float()) -> float(). pixel_coverage(Pixel_centre, Line_centre, Half_thick) -> Pixel_lo = Pixel_centre - 0.5, Pixel_hi = Pixel_centre + 0.5, Band_lo = Line_centre - Half_thick, Band_hi = Line_centre + Half_thick, Overlap_lo = gleam@float:max(Pixel_lo, Band_lo), Overlap_hi = gleam@float:min(Pixel_hi, Band_hi), Overlap = Overlap_hi - Overlap_lo, case Overlap =< +0.0 of true -> +0.0; false -> case Overlap >= 1.0 of true -> 1.0; false -> Overlap end end. -file("src/sparklinekit/internal/raster.gleam", 446). ?DOC(false). -spec plot_perpendicular( canvas(), integer(), float(), boolean(), float(), sparklinekit@internal@color:rgba() ) -> canvas(). plot_perpendicular(Canvas, Major, Minor_centre, Steep, Half_thick, Colour) -> Minor_lo = math:floor(Minor_centre - Half_thick), Minor_hi = math:ceil(Minor_centre + Half_thick), Lo_i = erlang:round(Minor_lo), Hi_i = erlang:round(Minor_hi), _pipe = range(Lo_i, Hi_i), gleam@list:fold( _pipe, Canvas, fun(C, Minor) -> Coverage = pixel_coverage( erlang:float(Minor), Minor_centre, Half_thick ), case Coverage > +0.0 of false -> C; true -> Tinted = sparklinekit@internal@color:with_alpha( Colour, Coverage ), {Px, Py} = case Steep of true -> {Minor, Major}; false -> {Major, Minor} end, put(C, Px, Py, Tinted) end end ). -file("src/sparklinekit/internal/raster.gleam", 408). ?DOC(false). -spec do_wu_line( canvas(), integer(), integer(), float(), float(), float(), boolean(), float(), sparklinekit@internal@color:rgba() ) -> canvas(). do_wu_line( Canvas, Major, End_major, Raw_major, Minor_centre, Gradient, Steep, Half_thick, Colour ) -> case Major > End_major of true -> Canvas; false -> Updated = plot_perpendicular( Canvas, Major, Minor_centre, Steep, Half_thick, Colour ), do_wu_line( Updated, Major + 1, End_major, Raw_major + 1.0, Minor_centre + Gradient, Gradient, Steep, Half_thick, Colour ) end. -file("src/sparklinekit/internal/raster.gleam", 108). ?DOC(false). -spec draw_line( canvas(), float(), float(), float(), float(), float(), sparklinekit@internal@color:rgba() ) -> canvas(). draw_line(Canvas, X0, Y0, X1, Y1, Thickness, Colour) -> Actual_thickness = case Thickness > +0.0 of true -> Thickness; false -> 1.0 end, Dx = X1 - X0, Dy = Y1 - Y0, Steep = gleam@float:absolute_value(Dy) > gleam@float:absolute_value(Dx), {X0_, Y0_, X1_, Y1_} = case Steep of true -> {Y0, X0, Y1, X1}; false -> {X0, Y0, X1, Y1} end, {X0_@1, Y0_@1, X1_@1, Y1_@1} = case X0_ > X1_ of true -> {X1_, Y1_, X0_, Y0_}; false -> {X0_, Y0_, X1_, Y1_} end, New_dx = X1_@1 - X0_@1, New_dy = Y1_@1 - Y0_@1, Gradient = case New_dx =:= +0.0 of true -> 1.0; false -> case New_dx of +0.0 -> +0.0; -0.0 -> -0.0; Gleam@denominator -> New_dy / Gleam@denominator end end, Start_x = erlang:round(X0_@1), End_x = erlang:round(X1_@1), Half_thick = Actual_thickness / 2.0, do_wu_line( Canvas, Start_x, End_x, erlang:float(Start_x), Y0_@1 + (Gradient * (erlang:float(Start_x) - X0_@1)), Gradient, Steep, Half_thick, Colour ). -file("src/sparklinekit/internal/raster.gleam", 497). ?DOC(false). -spec min_int(integer(), integer()) -> integer(). min_int(A, B) -> case A < B of true -> A; false -> B end. -file("src/sparklinekit/internal/raster.gleam", 504). ?DOC(false). -spec max_int(integer(), integer()) -> integer(). max_int(A, B) -> case A > B of true -> A; false -> B end. -file("src/sparklinekit/internal/raster.gleam", 322). ?DOC(false). -spec fill_rect_int( canvas(), integer(), integer(), integer(), integer(), sparklinekit@internal@color:rgba() ) -> canvas(). fill_rect_int(Canvas, X0, Y0, X1, Y1, Colour) -> X_lo = max_int(0, min_int(X0, X1)), X_hi = min_int(erlang:element(2, Canvas), max_int(X0, X1)), Y_lo = max_int(0, min_int(Y0, Y1)), Y_hi = min_int(erlang:element(3, Canvas), max_int(Y0, Y1)), case (X_hi =< X_lo) orelse (Y_hi =< Y_lo) of true -> Canvas; false -> Xs = range(X_lo, X_hi - 1), _pipe = range(Y_lo, Y_hi - 1), gleam@list:fold( _pipe, Canvas, fun(C, Y) -> gleam@list:fold( Xs, C, fun(C2, X) -> put(C2, X, Y, Colour) end ) end ) end. -file("src/sparklinekit/internal/raster.gleam", 60). ?DOC(false). -spec fill_rect( canvas(), float(), float(), float(), float(), sparklinekit@internal@color:rgba() ) -> canvas(). fill_rect(Canvas, X, Y, Width, Height, Colour) -> X0 = erlang:round(X), Y0 = erlang:round(Y), X1 = erlang:round(X + Width), Y1 = erlang:round(Y + Height), fill_rect_int(Canvas, X0, Y0, X1, Y1, Colour). -file("src/sparklinekit/internal/raster.gleam", 346). ?DOC(false). -spec rounded_rect_rows( canvas(), integer(), integer(), integer(), integer(), integer(), sparklinekit@internal@color:rgba() ) -> canvas(). rounded_rect_rows(Canvas, X0, Y0, X1, Y1, R, Colour) -> W = X1 - X0, H = Y1 - Y0, case (W =< 0) orelse (H =< 0) of true -> Canvas; false -> _pipe = range(0, H - 1), gleam@list:fold( _pipe, Canvas, fun(C, Dy) -> Inset = row_inset(Dy, H, R), Row_x0 = X0 + Inset, Row_x1 = X1 - Inset, case Row_x1 > Row_x0 of true -> fill_rect_int( C, Row_x0, Y0 + Dy, Row_x1, (Y0 + Dy) + 1, Colour ); false -> C end end ) end. -file("src/sparklinekit/internal/raster.gleam", 77). ?DOC(false). -spec fill_rounded_rect( canvas(), float(), float(), float(), float(), float(), sparklinekit@internal@color:rgba() ) -> canvas(). fill_rounded_rect(Canvas, X, Y, Width, Height, Radius, Colour) -> X0 = erlang:round(X), Y0 = erlang:round(Y), X1 = erlang:round(X + Width), Y1 = erlang:round(Y + Height), W = X1 - X0, H = Y1 - Y0, R = begin _pipe = Radius, _pipe@1 = gleam@float:min(_pipe, erlang:float(W) / 2.0), _pipe@2 = gleam@float:min(_pipe@1, erlang:float(H) / 2.0), gleam@float:max(_pipe@2, +0.0) end, R_int = erlang:round(R), case R_int =< 0 of true -> fill_rect_int(Canvas, X0, Y0, X1, Y1, Colour); false -> rounded_rect_rows(Canvas, X0, Y0, X1, Y1, R_int, Colour) end.