%% @doc Main interface for Erlang captcha library -module(ecaptcha). -export([pixels/2, gif/2, png/2, fonts/0]). -export_type([opts/0, effects/0, color_name/0, color_rgb/0, font_name/0, alphabet/0, err_reason/0]). -type opts() :: #{ color => color_name() | color_rgb(), effects => effects(), font => font_name(), alphabet => alphabet() }. %% `color' - what color to use for the gif/png image. predefined color name or RGB tuple; %% default: `black' %% `effects' - list of additional effects to apply to the text rendering; default: `[]' %% `font' - name of the font to use; default: `<<"hplhs-oldstyle">>' %% `alphabet' - set of characters to use to generate random string. One of predefined sets or %% binary string with all the allowed characters (as long as they are defined in selected font); %% default: latin_lowercase -type effects() :: [line | blur | filter | dots | reverse_dots]. %% `line' - draws a curved horisontal line on top of the text %% `blur' - blurs the image (averages each pixel's color with it's neighbours) %% `filter' - makes letters hollow %% `dots' - draws 100 random 2x2 white dots on top of the image, effectively removing small pathes %% from it %% `reverse_dots' - draws 20 random dots of a randomized size from 1 to 3px using a color opposite %% to the current color (so, reversing the color - black becomes white, white becomes black) -type color_name() :: ecaptcha_color:color_name(). -type color_rgb() :: ecaptcha_color:rgb(). -type font_name() :: binary(). -type alphabet() :: numbers | latin_lowercase | latin_uppercase | alphabet_bin(). -type alphabet_bin() :: binary(). -type err_reason() :: font_name_not_binary | font_not_found | chars_not_binary | wrong_chars_length | character_out_of_alphabet_range | bad_random | small_rand_binary | opts_not_list | non_atom_option | unknown_option. %% @doc Generate greyscale array of pixels 200x70 %% %% It returns a tuple where 1st element is a binary ASCII string which contains characters that are %% printed on captcha image. %% 2nd element is a plain binary that contains 200x70 = 14000 "pixels", 1 byte each, where %% 0 means black and 255 - white, intermediate values are shades of grey. %% %% @param NumChars how many characters should be on a image, `1..6' %% @param Opts map of additional options such as `font', `alphabet' and `effects' -spec pixels(NumChars :: pos_integer(), opts()) -> {Str :: binary(), Pixels :: binary()} | {error, err_reason()}. pixels(NumChars, Opts) -> <> = crypto:strong_rand_bytes( ecaptcha_nif:rand_size() + NumChars ), Chars = chars(CharsRand, maps:get(alphabet, Opts, latin_lowercase)), Font = maps:get(font, Opts, <<"hplhs-oldstyle">>), Effects = maps:get(effects, Opts, []), case ecaptcha_nif:pixels(Font, Chars, InnerRand, Effects) of {error, _} = Err -> Err; Pixels -> {Chars, Pixels} end. %% @doc Generate GIF image 200x70 with NumChars letters on it %% %% Same as {@link png/3}, but image is in GIF format. -spec gif(NumChars :: pos_integer(), opts()) -> {Str :: binary(), GifImg :: binary()} | {error, err_reason()}. gif(NumChars, Opts) -> img(NumChars, Opts, fun ecaptcha_gif:encode/4). %% @doc Generate PNG image 200x70 with NumChars letters on it %% %% It returns a 2-tuple where 1st element is the binary containing of `NumChars' size containing %% ASCII string that is printed on the image and 2nd element is PNG-encoded image that can be, eg %% sent directly to the browser with `Content-Type: image/png'. %% @param NumChars - same as in {@link pixels/2} %% @param Opts - same as in {@link pixels/2}, but also includes `color'. See {@link opts()}. -spec png(NumChars :: pos_integer(), opts()) -> {Str :: binary(), PngImg :: binary()} | {error, err_reason()}. png(NumChars, Opts) -> img(NumChars, Opts, fun ecaptcha_png:encode/4). %% @doc List avaliable fonts %% %% Returns a list of tuples where the 1st element is the font name and 2nd elemnt is the binary %% containing all the available characters of this font. %% Format may change in the future. -spec fonts() -> [{font_name(), alphabet_bin()}, ...]. fonts() -> ecaptcha_nif:fonts(). %% Internal img(NumChars, Opts, Encoder) -> case pixels(NumChars, Opts) of {error, _} = Err -> Err; {Str, Pixels} -> Color = maps:get(color, Opts, black), {Str, Encoder(Pixels, 200, 70, Color)} end. chars(Rand, AlphabetSelector) -> Alphabet = alphabet(AlphabetSelector), AlphabetSize = byte_size(Alphabet), <<<<(binary:at(Alphabet, C rem AlphabetSize))>> || <> <= Rand>>. alphabet(numbers) -> <<"0123456789">>; alphabet(latin_lowercase) -> <<"abcdefghijklmnopqrstuvwxyz">>; alphabet(latin_uppercase) -> <<"ABCDEFGHIJKLMNOPQRSTUVWXYZ">>; alphabet(Alphabet) when is_binary(Alphabet), byte_size(Alphabet) > 3 -> Alphabet.