Current section
Files
Jump to
Current section
Files
src/ansel@image.erl
-module(ansel@image).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]).
-export([from_bit_array/1, to_bit_array/2, to_pixel_matrix/1, from_pixel_matrix/1, new/3, extract_area/2, composite_over/4, fill/3, outline/4, blur/2, rotate/2, round/2, get_width/1, get_height/1, fit_bounding_box/2, border/3, to_bounding_box/1, scale/2, scale_width/2, scale_height/2, write/3, read/1, create_thumbnail/2]).
-export_type([image_format/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(
" This module is primarily a wrapper around the Elixir package Vix, which\n"
" is a wrapper around the great image processing library vips. A pre-built\n"
" vips binary comes with Vix, but see the readme for more information on\n"
" how to bring your own to support more image formats.\n"
" \n"
" This module uses the [snag package](https://hexdocs.pm/snag/index.html) for \n"
" error handling because vix errors just come back as strings and are not \n"
" enumerated. Make sure to install it as well to work with the error messages.\n"
" \n"
" ```gleam\n"
" import ansel\n"
" import ansel/image\n"
" import gleam/result\n"
" import snag\n"
"\n"
" pub fn main() {\n"
" let assert Ok(img) = image.read(\"input.jpeg\")\n"
"\n"
" image.scale(img, by: 2.0)\n"
" |> result.try(\n"
" image.write(_, \"output\", ansel.JPEG(quality: 60, keep_metadata: False)),\n"
" )\n"
" |> snag.context(\"Unable to process my cool image\")\n"
" }\n"
" // -> output.jpeg written to disk as a smaller, rounded, bordered version of the\n"
" // original image\n"
" ```\n"
).
-type image_format() :: {jpeg, integer(), boolean()} |
{jpeg2000, integer(), boolean()} |
{jpegxl, integer(), boolean()} |
png |
{web_p, integer(), boolean()} |
{avif, integer(), boolean()} |
{tiff, integer(), boolean()} |
{heic, integer(), boolean()} |
fits |
matlab |
pdf |
svg |
hdr |
ppm |
csv |
gif |
analyze |
n_if_ti |
deep_zoom |
{custom, binary()}.
-file("src/ansel/image.gleam", 103).
-spec format_common_options(integer(), boolean()) -> binary().
format_common_options(Quality, Keep_metadata) ->
<<<<<<<<"[Q="/utf8, (gleam@int:to_string(Quality))/binary>>/binary,
",strip="/utf8>>/binary,
(begin
_pipe = gleam@bool:to_string(not Keep_metadata),
gleam@string:lowercase(_pipe)
end)/binary>>/binary,
"]"/utf8>>.
-file("src/ansel/image.gleam", 71).
-spec image_format_to_string(image_format()) -> binary().
image_format_to_string(Format) ->
case Format of
{jpeg, Quality, Keep_metadata} ->
<<".jpeg"/utf8,
(format_common_options(Quality, Keep_metadata))/binary>>;
{jpeg2000, Quality@1, Keep_metadata@1} ->
<<".jp2"/utf8,
(format_common_options(Quality@1, Keep_metadata@1))/binary>>;
{jpegxl, Quality@2, Keep_metadata@2} ->
<<".jxl"/utf8,
(format_common_options(Quality@2, Keep_metadata@2))/binary>>;
png ->
<<".png"/utf8>>;
{web_p, Quality@3, Keep_metadata@3} ->
<<".webp"/utf8,
(format_common_options(Quality@3, Keep_metadata@3))/binary>>;
{avif, Quality@4, Keep_metadata@4} ->
<<".avif"/utf8,
(format_common_options(Quality@4, Keep_metadata@4))/binary>>;
{tiff, Quality@5, Keep_metadata@5} ->
<<".tiff"/utf8,
(format_common_options(Quality@5, Keep_metadata@5))/binary>>;
{heic, Quality@6, Keep_metadata@6} ->
<<".heic"/utf8,
(format_common_options(Quality@6, Keep_metadata@6))/binary>>;
fits ->
<<".fits"/utf8>>;
matlab ->
<<".mat"/utf8>>;
pdf ->
<<".pdf"/utf8>>;
svg ->
<<".svg"/utf8>>;
hdr ->
<<".hdr"/utf8>>;
ppm ->
<<".ppm"/utf8>>;
csv ->
<<".csv"/utf8>>;
gif ->
<<".gif"/utf8>>;
analyze ->
<<".analyze"/utf8>>;
n_if_ti ->
<<".nii"/utf8>>;
deep_zoom ->
<<".dzi"/utf8>>;
{custom, Format@1} ->
Format@1
end.
-file("src/ansel/image.gleam", 153).
?DOC(
" Reads a vips image from a bit array\n"
" \n"
" ## Example\n"
" ```gleam\n"
" simplifile.read_bits(\"input.jpeg\")\n"
" |> result.try(image.from_bit_array)\n"
" // -> Ok(ansel.Image)\n"
" ```\n"
).
-spec from_bit_array(bitstring()) -> {ok, ansel:image()} | {error, snag:snag()}.
from_bit_array(Bin) ->
_pipe = 'Elixir.Vix.Vips.Image':new_from_buffer(Bin),
_pipe@1 = gleam@result:map_error(_pipe, fun snag:new/1),
snag:context(_pipe@1, <<"Unable to read image from bit array"/utf8>>).
-file("src/ansel/image.gleam", 171).
?DOC(
" Saves a vips image to a bit array. Assumes your vips was built with the\n"
" correct encoder support for the format to save in.\n"
" \n"
" ## Example\n"
" ```gleam\n"
" image.new(6, 6, color.GleamLucy)\n"
" |> result.map(to_bit_array(_, ansel.PNG))\n"
" |> result.try(simplifile.write_bits(\"output.png\"))\n"
" ```\n"
).
-spec to_bit_array(ansel:image(), image_format()) -> bitstring().
to_bit_array(Img, Format) ->
'Elixir.Ansel':to_bit_array(Img, image_format_to_string(Format)).
-file("src/ansel/image.gleam", 189).
?DOC(
" Converts a vips image into a matrix of pixel values.\n"
"\n"
" ## Example\n"
" ```gleam \n"
" image.new(6, 6, GleamLucy)\n"
" |> image.to_pixel_matrix\n"
" // -> Ok([[RGB(255, 175, 243), ...],\n"
" [RGB(255, 175, 243), ...],\n"
" ...\n"
" [RGB(255, 175, 243), ...]])\n"
" ```\n"
).
-spec to_pixel_matrix(ansel:image()) -> {ok, list(list(ansel@color:color()))} |
{error, snag:snag()}.
to_pixel_matrix(Img) ->
_pipe = Img,
_pipe@1 = 'Elixir.Ansel':to_rgb_list(_pipe),
_pipe@4 = gleam@result:map(_pipe@1, fun(Pixels) -> _pipe@2 = Pixels,
gleam@list:map(_pipe@2, fun(Pixel_row) -> _pipe@3 = Pixel_row,
gleam@list:map(
_pipe@3,
fun(Pixel) ->
[R, G, B] = case Pixel of
[_, _, _] -> Pixel;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
value => _assert_fail,
module => <<"ansel/image"/utf8>>,
function => <<"to_pixel_matrix"/utf8>>,
line => 199})
end,
{rgb, R, G, B}
end
) end) end),
_pipe@5 = gleam@result:map_error(_pipe@4, fun snag:new/1),
snag:context(_pipe@5, <<"Unable to read image into pixel list"/utf8>>).
-file("src/ansel/image.gleam", 220).
?DOC(
" Converts a matrix of pixel values into a vips image.\n"
"\n"
" ## Example\n"
" ```gleam \n"
" list.repeat(list.repeat(RGB(255, 175, 243), 6), 6)\n"
" |> image.from_pixel_matrix\n"
" // -> Ok(ansel.Image) \n"
" // Same as image.new(6, 6, GleamLucy)\n"
" ```\n"
).
-spec from_pixel_matrix(list(list(ansel@color:color()))) -> {ok, ansel:image()} |
{error, snag:snag()}.
from_pixel_matrix(Pixels) ->
Height = erlang:length(Pixels),
Width = begin
_pipe = Pixels,
_pipe@1 = gleam@list:first(_pipe),
_pipe@2 = gleam@result:unwrap(_pipe@1, []),
erlang:length(_pipe@2)
end,
_pipe@3 = Pixels,
_pipe@4 = gleam@list:flatten(_pipe@3),
_pipe@5 = gleam@list:map(
_pipe@4,
fun(Rgb) ->
{rgb, R, G, B} = case Rgb of
{rgb, _, _, _} -> Rgb;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
value => _assert_fail,
module => <<"ansel/image"/utf8>>,
function => <<"from_pixel_matrix"/utf8>>,
line => 228})
end,
<<R, G, B>>
end
),
_pipe@6 = gleam_stdlib:bit_array_concat(_pipe@5),
_pipe@7 = 'Elixir.Ansel':from_binary(_pipe@6, Height, Width),
_pipe@8 = gleam@result:map_error(_pipe@7, fun snag:new/1),
snag:context(
_pipe@8,
<<"Unable to convert rgb list into ansel image"/utf8>>
).
-file("src/ansel/image.gleam", 251).
?DOC(
" Creates a new image with the specified width, height, and color\n"
" \n"
" ## Example\n"
" ```gleam\n"
" image.new(6, 6, color.Olive)\n"
" // -> Ok(ansel.Image)\n"
" ```\n"
).
-spec new(integer(), integer(), ansel@color:color()) -> {ok, ansel:image()} |
{error, snag:snag()}.
new(Width, Height, Color) ->
_pipe = 'Elixir.Ansel':new_image(Width, Height, ansel@color:to_bands(Color)),
_pipe@1 = gleam@result:map_error(_pipe, fun snag:new/1),
snag:context(_pipe@1, <<"Unable to create new image"/utf8>>).
-file("src/ansel/image.gleam", 270).
?DOC(
" Extracts an area out of an image, resulting in a new image of the \n"
" extracted area.\n"
).
-spec extract_area(ansel:image(), ansel@bounding_box:bounding_box()) -> {ok,
ansel:image()} |
{error, snag:snag()}.
extract_area(Image, Bounding_box) ->
{Left, Top, Width, Height} = ansel@bounding_box:to_ltwh_tuple(Bounding_box),
_pipe = 'Elixir.Vix.Vips.Operation':extract_area(
Image,
Left,
Top,
Width,
Height
),
_pipe@1 = gleam@result:map_error(_pipe, fun snag:new/1),
snag:context(_pipe@1, <<"Unable to extract area from image"/utf8>>).
-file("src/ansel/image.gleam", 292).
?DOC(
" Places an image over another image, with the top left corner of the\n"
" overlay image placed at the specified coordinates.\n"
).
-spec composite_over(ansel:image(), ansel:image(), integer(), integer()) -> {ok,
ansel:image()} |
{error, snag:snag()}.
composite_over(Base, Overlay, L, T) ->
_pipe = 'Elixir.Ansel':composite_over(Base, Overlay, L, T),
_pipe@1 = gleam@result:map_error(_pipe, fun snag:new/1),
snag:context(
_pipe@1,
<<"Unable to composite overlay image over base image"/utf8>>
).
-file("src/ansel/image.gleam", 312).
?DOC(" Fills in an area of the passed image with a solid color.\n").
-spec fill(
ansel:image(),
ansel@bounding_box:bounding_box(),
ansel@color:color()
) -> {ok, ansel:image()} | {error, snag:snag()}.
fill(Image, Bounding_box, Color) ->
{Left, Top, Width, Height} = ansel@bounding_box:to_ltwh_tuple(Bounding_box),
_pipe = new(Width, Height, Color),
gleam@result:'try'(
_pipe,
fun(_capture) -> composite_over(Image, _capture, Left, Top) end
).
-file("src/ansel/image.gleam", 325).
?DOC(
" Outlines an area in the passed image with a solid color. All\n"
" outline pixels are written inside the bounding box area.\n"
).
-spec outline(
ansel:image(),
ansel@bounding_box:bounding_box(),
ansel@color:color(),
integer()
) -> {ok, ansel:image()} | {error, snag:snag()}.
outline(Image, Bounding_box, Color, Thickness) ->
{Left, Top, Width, Height} = ansel@bounding_box:to_ltwh_tuple(Bounding_box),
Original_bb = begin
_pipe = Bounding_box,
ansel@bounding_box:shrink(_pipe, Thickness)
end,
gleam@result:'try'(
new(Width, Height, Color),
fun(Outline) ->
gleam@result:'try'(
composite_over(Image, Outline, Left, Top),
fun(Filled) -> case Original_bb of
{ok, Original_bb@1} ->
{Original_left, Original_top, _, _} = ansel@bounding_box:to_ltwh_tuple(
Original_bb@1
),
gleam@result:'try'(
extract_area(Image, Original_bb@1),
fun(Original_area) ->
composite_over(
Filled,
Original_area,
Original_left,
Original_top
)
end
);
{error, nil} ->
{ok, Filled}
end end
)
end
).
-file("src/ansel/image.gleam", 386).
?DOC(" Applies a gaussian blur with the given sigma value to an image.\n").
-spec blur(ansel:image(), float()) -> {ok, ansel:image()} | {error, snag:snag()}.
blur(Image, Sigma) ->
_pipe = 'Elixir.Vix.Vips.Operation':gaussblur(Image, Sigma),
_pipe@1 = gleam@result:map_error(_pipe, fun snag:new/1),
snag:context(_pipe@1, <<"Unable to blur image"/utf8>>).
-file("src/ansel/image.gleam", 399).
?DOC(" Rotates an image by the given number of degrees.\n").
-spec rotate(ansel:image(), float()) -> {ok, ansel:image()} |
{error, snag:snag()}.
rotate(Image, Degrees) ->
_pipe = case Degrees of
+0.0 ->
{ok, Image};
90.0 ->
'Elixir.Ansel':rotate90(Image);
180.0 ->
'Elixir.Ansel':rotate180(Image);
270.0 ->
'Elixir.Ansel':rotate270(Image);
_ ->
'Elixir.Vix.Vips.Operation':rotate(Image, Degrees)
end,
_pipe@1 = gleam@result:map_error(_pipe, fun snag:new/1),
snag:context(_pipe@1, <<"Unable to rotate image"/utf8>>).
-file("src/ansel/image.gleam", 433).
?DOC(
" Rounds the corners of an image by a given radius, leaving transparent \n"
" pixels where the rounding was applied. A large radius can be given to \n"
" create circular images. This can be used with the border function to \n"
" create rounded borders around images. This implmentation is heavily \n"
" inspired by the extensive elixir library Image.\n"
).
-spec round(ansel:image(), float()) -> {ok, ansel:image()} |
{error, snag:snag()}.
round(Image, Radius) ->
_pipe = 'Elixir.Ansel':round(Image, Radius),
_pipe@1 = gleam@result:map_error(_pipe, fun snag:new/1),
snag:context(_pipe@1, <<"Unable to round image"/utf8>>).
-file("src/ansel/image.gleam", 447).
?DOC(" Returns the width of an image.\n").
-spec get_width(ansel:image()) -> integer().
get_width(Image) ->
'Elixir.Vix.Vips.Image':width(Image).
-file("src/ansel/image.gleam", 451).
?DOC(" Returns the height of an image.\n").
-spec get_height(ansel:image()) -> integer().
get_height(Image) ->
'Elixir.Vix.Vips.Image':height(Image).
-file("src/ansel/image.gleam", 122).
?DOC(
" Fits a fixed bounding box to an image by dropping any pixels outside the\n"
" dimensions of the image.\n"
" \n"
" ## Example\n"
" \n"
" ```gleam\n"
" let assert Ok(bb) = bounding_box.ltwh(4, 2, 40, 30)\n"
" let assert Ok(img) = image.new(6, 7, color.GleamLucy)\n"
" image.fit_bounding_box(bb, in: img)\n"
" // -> bounding_box.ltwh(left: 4, top: 2, width: 2, height: 5)\n"
" ```\n"
).
-spec fit_bounding_box(ansel@bounding_box:bounding_box(), ansel:image()) -> {ok,
ansel@bounding_box:bounding_box()} |
{error, snag:snag()}.
fit_bounding_box(Bounding_box, Image) ->
Width = 'Elixir.Vix.Vips.Image':width(Image),
Height = 'Elixir.Vix.Vips.Image':height(Image),
{Left, Top, Right, Bottom} = ansel@bounding_box:to_ltrb_tuple(Bounding_box),
case {Left < Width, Top < Height} of
{true, true} ->
ansel@bounding_box:ltrb(
Left,
Top,
gleam@int:min(Right, Width),
gleam@int:min(Bottom, Height)
);
{_, _} ->
snag:error(
<<"Passed bounding box is completely outside the image to fit"/utf8>>
)
end.
-file("src/ansel/image.gleam", 367).
?DOC(
" Add a solid border around the passed image, expanding the \n"
" dimensions of the image by the border thickness. Replaces any transparent\n"
" pixels with the color of the border. This can be used with the round\n"
" function to add a rounded border to an image.\n"
).
-spec border(ansel:image(), ansel@color:color(), integer()) -> {ok,
ansel:image()} |
{error, snag:snag()}.
border(Image, Color, Thickness) ->
Height = 'Elixir.Vix.Vips.Image':height(Image),
Width = 'Elixir.Vix.Vips.Image':width(Image),
gleam@result:'try'(
begin
_pipe = ansel@bounding_box:ltwh(0, 0, Width, Height),
_pipe@1 = gleam@result:map(
_pipe,
fun(_capture) ->
ansel@bounding_box:expand(_capture, Thickness)
end
),
gleam@result:map(_pipe@1, fun ansel@bounding_box:to_ltwh_tuple/1)
end,
fun(_use0) ->
{_, _, Outline_width, Outline_height} = _use0,
_pipe@2 = new(Outline_width, Outline_height, Color),
gleam@result:'try'(
_pipe@2,
fun(_capture@1) ->
composite_over(_capture@1, Image, Thickness, Thickness)
end
)
end
).
-file("src/ansel/image.gleam", 455).
?DOC(
" Returns the dimensions of an image as a bounding box. Useful for bounding\n"
" box operations.\n"
).
-spec to_bounding_box(ansel:image()) -> ansel@bounding_box:bounding_box().
to_bounding_box(Image) ->
ansel@bounding_box:unchecked_ltwh(
0,
0,
'Elixir.Vix.Vips.Image':width(Image),
'Elixir.Vix.Vips.Image':height(Image)
).
-file("src/ansel/image.gleam", 481).
?DOC(" Resizes an image by the given scale, preserving the aspect ratio.\n").
-spec scale(ansel:image(), float()) -> {ok, ansel:image()} |
{error, snag:snag()}.
scale(Img, Scale) ->
_pipe = 'Elixir.Vix.Vips.Operation':resize(Img, Scale),
_pipe@1 = gleam@result:map_error(_pipe, fun snag:new/1),
snag:context(_pipe@1, <<"Unable to resize image"/utf8>>).
-file("src/ansel/image.gleam", 465).
?DOC(" Scales an image to the given width, preserving the aspect ratio.\n").
-spec scale_width(ansel:image(), integer()) -> {ok, ansel:image()} |
{error, snag:snag()}.
scale_width(Img, Target) ->
scale(Img, case gleam@int:to_float('Elixir.Vix.Vips.Image':width(Img)) of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> gleam@int:to_float(Target) / Gleam@denominator
end).
-file("src/ansel/image.gleam", 473).
?DOC(" Scales an image to the given height, preserving the aspect ratio.\n").
-spec scale_height(ansel:image(), integer()) -> {ok, ansel:image()} |
{error, snag:snag()}.
scale_height(Img, Target) ->
scale(Img, case gleam@int:to_float('Elixir.Vix.Vips.Image':height(Img)) of
+0.0 -> +0.0;
-0.0 -> -0.0;
Gleam@denominator -> gleam@int:to_float(Target) / Gleam@denominator
end).
-file("src/ansel/image.gleam", 494).
?DOC(" Writes an image to the specified path in the specified format.\n").
-spec write(ansel:image(), binary(), image_format()) -> {ok, nil} |
{error, snag:snag()}.
write(Img, Path, Format) ->
_pipe = 'Elixir.Ansel':write_to_file(
Img,
<<Path/binary, (image_format_to_string(Format))/binary>>
),
_pipe@1 = gleam@result:map_error(_pipe, fun snag:new/1),
snag:context(_pipe@1, <<"Unable to write image to file"/utf8>>).
-file("src/ansel/image.gleam", 508).
?DOC(" Reads an image from the specified path.\n").
-spec read(binary()) -> {ok, ansel:image()} | {error, snag:snag()}.
read(Path) ->
_pipe = 'Elixir.Ansel':read(Path),
_pipe@1 = gleam@result:map_error(_pipe, fun snag:new/1),
snag:context(_pipe@1, <<"Unable to read image from file"/utf8>>).
-file("src/ansel/image.gleam", 518).
?DOC(" Creates a thumbnail of an image at the specified path with the given width.\n").
-spec create_thumbnail(binary(), integer()) -> {ok, ansel:image()} |
{error, snag:snag()}.
create_thumbnail(Path, Width) ->
_pipe = 'Elixir.Vix.Vips.Operation':thumbnail(Path, Width),
_pipe@1 = gleam@result:map_error(_pipe, fun snag:new/1),
snag:context(_pipe@1, <<"Unable to create thumbnail from file"/utf8>>).