src/qrkit@render@png.erl

-module(qrkit@render@png).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/qrkit/render/png.gleam").
-export([to_bit_array/3]).

-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(" Pure Gleam PNG renderer for qrkit QR codes.\n").

-file("src/qrkit/render/png.gleam", 56).
-spec pad_rows(list(list(boolean())), integer()) -> list(list(boolean())).
pad_rows(Rows, Margin) ->
    Width = case Rows of
        [First | _] ->
            erlang:length(First);

        [] ->
            0
    end,
    Padding = gleam@list:repeat(false, Width + (Margin * 2)),
    Body = begin
        _pipe = Rows,
        gleam@list:map(
            _pipe,
            fun(Row) ->
                lists:append(
                    gleam@list:repeat(false, Margin),
                    lists:append(Row, gleam@list:repeat(false, Margin))
                )
            end
        )
    end,
    lists:append(
        gleam@list:repeat(Padding, Margin),
        lists:append(Body, gleam@list:repeat(Padding, Margin))
    ).

-file("src/qrkit/render/png.gleam", 92).
-spec prepend_repeat(GFP, integer(), list(GFP)) -> list(GFP).
prepend_repeat(Value, Count, Acc) ->
    case Count =< 0 of
        true ->
            Acc;

        false ->
            prepend_repeat(Value, Count - 1, [Value | Acc])
    end.

-file("src/qrkit/render/png.gleam", 85).
-spec scale_row(list(boolean()), integer(), list(boolean())) -> list(boolean()).
scale_row(Row, Scale, Acc) ->
    case Row of
        [] ->
            lists:reverse(Acc);

        [Value | Rest] ->
            scale_row(Rest, Scale, prepend_repeat(Value, Scale, Acc))
    end.

-file("src/qrkit/render/png.gleam", 77).
-spec scale_rows(list(list(boolean())), integer()) -> list(list(boolean())).
scale_rows(Rows, Scale) ->
    _pipe = Rows,
    gleam@list:flat_map(
        _pipe,
        fun(Row) ->
            Scaled = scale_row(Row, Scale, []),
            gleam@list:repeat(Scaled, Scale)
        end
    ).

-file("src/qrkit/render/png.gleam", 99).
-spec pixels_width(list(list(boolean()))) -> integer().
pixels_width(Rows) ->
    case Rows of
        [First | _] ->
            erlang:length(First);

        [] ->
            0
    end.

-file("src/qrkit/render/png.gleam", 140).
-spec pixel_bit(boolean()) -> integer().
pixel_bit(Pixel) ->
    case Pixel of
        true ->
            0;

        false ->
            1
    end.

-file("src/qrkit/render/png.gleam", 186).
-spec take_bytes(list(integer()), integer(), list(integer())) -> {list(integer()),
    list(integer())}.
take_bytes(Values, Count, Acc) ->
    case {Values, Count} of
        {Rest, 0} ->
            {lists:reverse(Acc), Rest};

        {[Value | Rest@1], _} ->
            take_bytes(Rest@1, Count - 1, [Value | Acc]);

        {[], _} ->
            {lists:reverse(Acc), []}
    end.

-file("src/qrkit/render/png.gleam", 254).
-spec byte_list_to_bit_array(list(integer())) -> bitstring().
byte_list_to_bit_array(Bytes) ->
    _pipe = Bytes,
    _pipe@1 = gleam@list:map(_pipe, fun(Byte) -> <<Byte>> end),
    gleam_stdlib:bit_array_concat(_pipe@1).

-file("src/qrkit/render/png.gleam", 260).
-spec prepend_reversed(list(GGW), list(GGW)) -> list(GGW).
prepend_reversed(Values, Acc) ->
    case Values of
        [] ->
            Acc;

        [Value | Rest] ->
            prepend_reversed(Rest, [Value | Acc])
    end.

-file("src/qrkit/render/png.gleam", 267).
-spec positive_or(integer(), integer()) -> integer().
positive_or(Value, Default) ->
    case Value > 0 of
        true ->
            Value;

        false ->
            Default
    end.

-file("src/qrkit/render/png.gleam", 274).
-spec non_negative_or(integer(), integer()) -> integer().
non_negative_or(Value, Default) ->
    case Value >= 0 of
        true ->
            Value;

        false ->
            Default
    end.

-file("src/qrkit/render/png.gleam", 281).
-spec u32_bytes(integer()) -> list(integer()).
u32_bytes(Value) ->
    [erlang:'band'(erlang:'bsr'(Value, 24), 16#FF),
        erlang:'band'(erlang:'bsr'(Value, 16), 16#FF),
        erlang:'band'(erlang:'bsr'(Value, 8), 16#FF),
        erlang:'band'(Value, 16#FF)].

-file("src/qrkit/render/png.gleam", 290).
-spec low_byte(integer()) -> integer().
low_byte(Value) ->
    erlang:'band'(Value, 16#FF).

-file("src/qrkit/render/png.gleam", 294).
-spec high_byte(integer()) -> integer().
high_byte(Value) ->
    erlang:'band'(erlang:'bsr'(Value, 8), 16#FF).

-file("src/qrkit/render/png.gleam", 162).
-spec do_deflate_store_blocks(list(integer()), list(integer())) -> list(integer()).
do_deflate_store_blocks(Data, Acc) ->
    case Data of
        [] ->
            lists:reverse(Acc);

        _ ->
            {Chunk_bytes, Rest} = take_bytes(Data, 65535, []),
            Final = case Rest of
                [] ->
                    1;

                _ ->
                    0
            end,
            Length = erlang:length(Chunk_bytes),
            Complement = 65535 - Length,
            Next = [Final,
                low_byte(Length),
                high_byte(Length),
                low_byte(Complement),
                high_byte(Complement) |
                Chunk_bytes],
            do_deflate_store_blocks(Rest, prepend_reversed(Next, Acc))
    end.

-file("src/qrkit/render/png.gleam", 158).
-spec deflate_store_blocks(list(integer())) -> list(integer()).
deflate_store_blocks(Data) ->
    do_deflate_store_blocks(Data, []).

-file("src/qrkit/render/png.gleam", 298).
-spec power_of_two(integer()) -> integer().
power_of_two(Exponent) ->
    case Exponent =< 0 of
        true ->
            1;

        false ->
            2 * power_of_two(Exponent - 1)
    end.

-file("src/qrkit/render/png.gleam", 147).
-spec finish_byte(integer(), integer()) -> integer().
finish_byte(Current, Bit_count) ->
    Padding_bits = 8 - Bit_count,
    (Current * power_of_two(Padding_bits)) + (power_of_two(Padding_bits) - 1).

-file("src/qrkit/render/png.gleam", 117).
-spec pack_bits(list(boolean()), integer(), integer(), list(integer())) -> list(integer()).
pack_bits(Pixels, Current, Bit_count, Acc) ->
    case Pixels of
        [] ->
            case Bit_count =:= 0 of
                true ->
                    lists:reverse(Acc);

                false ->
                    lists:reverse([finish_byte(Current, Bit_count) | Acc])
            end;

        [Pixel | Rest] ->
            Next = (Current * 2) + pixel_bit(Pixel),
            Next_count = Bit_count + 1,
            case Next_count =:= 8 of
                true ->
                    pack_bits(Rest, 0, 0, [Next | Acc]);

                false ->
                    pack_bits(Rest, Next, Next_count, Acc)
            end
    end.

-file("src/qrkit/render/png.gleam", 113).
-spec pack_scanline(list(boolean())) -> list(integer()).
pack_scanline(Row) ->
    [0 | pack_bits(Row, 0, 0, [])].

-file("src/qrkit/render/png.gleam", 106).
-spec scanlines(list(list(boolean())), list(integer())) -> list(integer()).
scanlines(Rows, Acc) ->
    case Rows of
        [] ->
            lists:reverse(Acc);

        [Row | Rest] ->
            scanlines(Rest, prepend_reversed(pack_scanline(Row), Acc))
    end.

-file("src/qrkit/render/png.gleam", 222).
-spec crc32_byte(integer(), integer()) -> integer().
crc32_byte(Crc, Remaining) ->
    case Remaining =< 0 of
        true ->
            Crc;

        false ->
            Next = case erlang:'band'(Crc, 1) =:= 1 of
                true ->
                    erlang:'bxor'(erlang:'bsr'(Crc, 1), 16#EDB88320);

                false ->
                    erlang:'bsr'(Crc, 1)
            end,
            crc32_byte(Next, Remaining - 1)
    end.

-file("src/qrkit/render/png.gleam", 214).
-spec do_crc32(list(integer()), integer()) -> integer().
do_crc32(Bytes, Crc) ->
    case Bytes of
        [] ->
            Crc;

        [Byte | Rest] ->
            do_crc32(Rest, crc32_byte(erlang:'bxor'(Crc, Byte), 8))
    end.

-file("src/qrkit/render/png.gleam", 209).
-spec crc32(list(integer())) -> integer().
crc32(Bytes) ->
    _pipe = do_crc32(Bytes, 16#FFFFFFFF),
    erlang:'bxor'(_pipe, 16#FFFFFFFF).

-file("src/qrkit/render/png.gleam", 198).
-spec chunk(list(integer()), list(integer())) -> list(integer()).
chunk(Type_bytes, Data_bytes) ->
    Crc = crc32(lists:append(Type_bytes, Data_bytes)),
    lists:append(
        u32_bytes(erlang:length(Data_bytes)),
        lists:append(Type_bytes, lists:append(Data_bytes, u32_bytes(Crc)))
    ).

-file("src/qrkit/render/png.gleam", 243).
-spec do_adler32(list(integer()), integer(), integer()) -> integer().
do_adler32(Bytes, S1, S2) ->
    case Bytes of
        [] ->
            (S2 * 65536) + S1;

        [Byte | Rest] ->
            Next_s1 = case 65521 of
                0 -> 0;
                Gleam@denominator -> (S1 + Byte) rem Gleam@denominator
            end,
            Next_s2 = case 65521 of
                0 -> 0;
                Gleam@denominator@1 -> (S2 + Next_s1) rem Gleam@denominator@1
            end,
            do_adler32(Rest, Next_s1, Next_s2)
    end.

-file("src/qrkit/render/png.gleam", 239).
-spec adler32(list(integer())) -> integer().
adler32(Bytes) ->
    do_adler32(Bytes, 1, 0).

-file("src/qrkit/render/png.gleam", 152).
-spec zlib_store(list(integer())) -> list(integer()).
zlib_store(Data) ->
    Checksum = adler32(Data),
    _pipe = lists:append([120, 1], deflate_store_blocks(Data)),
    lists:append(_pipe, u32_bytes(Checksum)).

-file("src/qrkit/render/png.gleam", 18).
?DOC(
    " Render a QR code as PNG bytes.\n"
    "\n"
    " `scale` values less than 1 are normalised to 1, and negative `margin`\n"
    " values are normalised to 0.\n"
).
-spec to_bit_array(qrkit:qr_code(), integer(), integer()) -> bitstring().
to_bit_array(Qr, Scale, Margin) ->
    Actual_scale = positive_or(Scale, 1),
    Actual_margin = non_negative_or(Margin, 0),
    Pixels = begin
        _pipe = qrkit:rows(Qr),
        _pipe@1 = pad_rows(_pipe, Actual_margin),
        scale_rows(_pipe@1, Actual_scale)
    end,
    Width = pixels_width(Pixels),
    Height = erlang:length(Pixels),
    Image_data = scanlines(Pixels, []),
    Ihdr = lists:append(
        u32_bytes(Width),
        lists:append(u32_bytes(Height), [1, 3, 0, 0, 0])
    ),
    Plte = [0, 0, 0, 255, 255, 255],
    Idat = zlib_store(Image_data),
    _pipe@2 = lists:append(
        [137, 80, 78, 71, 13, 10, 26, 10],
        lists:append(
            chunk([73, 72, 68, 82], Ihdr),
            lists:append(
                chunk([80, 76, 84, 69], Plte),
                lists:append(
                    chunk([73, 68, 65, 84], Idat),
                    chunk([73, 69, 78, 68], [])
                )
            )
        )
    ),
    byte_list_to_bit_array(_pipe@2).