Skip to main content

src/sparklinekit@internal@raster.erl

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