Skip to content

Commit

Permalink
switch to libvips for image processing (#784)
Browse files Browse the repository at this point in the history
  • Loading branch information
zkat authored Feb 17, 2024
1 parent 8b896d5 commit 6b767dd
Show file tree
Hide file tree
Showing 11 changed files with 27 additions and 57 deletions.
6 changes: 2 additions & 4 deletions lib/banchan/accounts/accounts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -530,10 +530,8 @@ defmodule Banchan.Accounts do

resp = HTTPoison.get!(url <> "?size=1024")
File.write!(tmp_file, resp.body)
%{format: format} = Mogrify.identify(tmp_file)

header =
make_header_image!(user, user, tmp_file, "image/#{format}", Path.basename(tmp_file))
header = make_header_image!(user, user, tmp_file, "image/png", Path.basename(tmp_file))

File.rm!(tmp_file)

Expand Down Expand Up @@ -593,7 +591,7 @@ defmodule Banchan.Accounts do

resp = HTTPoison.get!(url <> "?size=160")
File.write!(tmp_file, resp.body)
%{format: format} = Mogrify.identify(tmp_file)
"." <> format = Path.extname(url)

{pfp, thumb} =
make_pfp_images!(user, user, tmp_file, "image/#{format}", Path.basename(tmp_file))
Expand Down
11 changes: 5 additions & 6 deletions lib/banchan/uploads/uploads.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,15 @@ defmodule Banchan.Uploads do
alias Banchan.Repo
alias Banchan.Uploads.Upload

# image/heic needs additional support for Ubuntu. See https://askubuntu.com/questions/1131996/problems-with-compiling-imagemagick-with-heic
# Supported image formats for prebuilt libvips binaries:
# https://github.com/akash-akya/vix?tab=readme-ov-file#pre-compiled-nif-and-libvips
@image_formats ~w(
image/bmp image/gif image/png image/jpeg image/jpg
image/psd image/vnd.adobe.photoshop image/x-icon
image/vnd.microsoft.icon image/tiff image/webp
image/svg+xml image/x-xcf
image/gif image/png image/jpeg image/jpg
image/tiff image/webp image/svg+xml
)

@image_format_extensions ~w(
.bmp .gif .png .jpeg .jpg .psd .ico .tiff .webp .svg
.gif .png .jpeg .jpg .tiff .webp .svg
)

@video_formats ~w(
Expand Down
48 changes: 9 additions & 39 deletions lib/banchan/workers/thumbnailer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -131,45 +131,15 @@ defmodule Banchan.Workers.Thumbnailer do

File.mkdir_p!(Path.dirname(tmp_dest))

# https://www.smashingmagazine.com/2015/06/efficient-image-resizing-with-imagemagick/
Mogrify.open(tmp_src)
|> Mogrify.custom("flatten")
|> Mogrify.format(opts["format"])
|> Mogrify.limit("area", "128MB")
|> Mogrify.limit("disk", "1GB")
|> Mogrify.custom("filter", "Triangle")
|> Mogrify.custom("define", "filter:support=2")
|> Mogrify.custom("unsharp", "0.25x0.25+8+0.065")
|> Mogrify.custom("dither", "None")
|> Mogrify.custom("posterize", "136")
|> Mogrify.custom("quality", "82")
|> Mogrify.custom("define", "jpeg:fancy-upsampling=off")
|> Mogrify.custom("define", "png:compression-filter=5")
|> Mogrify.custom("define", "png:compression-level=9")
|> Mogrify.custom("define", "png:compression-strategy=1")
|> Mogrify.custom("define", "png:exclude-chunk=all")
|> Mogrify.custom("interlace", "none")
|> Mogrify.custom("colorspace", "sRGB")
|> Mogrify.custom("strip")
|> then(fn mog ->
if opts["target_size"] do
mog
|> Mogrify.custom("define", "#{opts["format"]}:extent=#{opts["target_size"]}")
else
mog
end
end)
|> then(fn mog ->
if opts["dimensions"] do
mog
|> Mogrify.gravity("Center")
|> Mogrify.custom("thumbnail", opts["dimensions"])
|> Mogrify.custom("extent", opts["dimensions"])
else
mog
end
end)
|> Mogrify.save(path: tmp_dest)
Image.thumbnail!(tmp_src, opts["dimensions"] || "512",
resize:
if opts["upscale"] do
:both
else
:down
end
)
|> Image.write!(tmp_dest, strip_metadata: true, minimize_file_size: true)

Uploads.upload_file!(dest, tmp_dest)

Expand Down
2 changes: 1 addition & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,11 @@ defmodule Banchan.MixProject do
{:html_sanitize_ex, "~> 1.4.2"},
# TODO: Can't bump this one to ~>2 because of tentacat
{:httpoison, "~> 1.8.1"},
{:image, "~> 0.37"},
{:jason, "~> 1.4.0"},
{:libcluster, "~> 3.3"},
{:makeup_elixir, ">= 0.0.0"},
{:makeup_erlang, ">= 0.0.0"},
{:mogrify, "~> 0.9.3"},
{:money, "~> 1.12.2"},
{:nimble_publisher, "~> 1.0.0"},
{:nimble_totp, "~> 1.0.0"},
Expand Down
4 changes: 4 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"bamboo_phoenix": {:hex, :bamboo_phoenix, "1.0.0", "f3cc591ffb163ed0bf935d256f1f4645cd870cf436545601215745fb9cc9953f", [:mix], [{:bamboo, ">= 2.0.0", [hex: :bamboo, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.3.0", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "6db88fbb26019c84a47994bb2bd879c0887c29ce6c559bc6385fd54eb8b37dee"},
"bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"},
"castore": {:hex, :castore, "1.0.3", "7130ba6d24c8424014194676d608cb989f62ef8039efd50ff4b3f33286d06db8", [:mix], [], "hexpm", "680ab01ef5d15b161ed6a95449fac5c6b8f60055677a8e79acf01b27baa4390b"},
"cc_precompiler": {:hex, :cc_precompiler, "0.1.9", "e8d3364f310da6ce6463c3dd20cf90ae7bbecbf6c5203b98bf9b48035592649b", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "9dcab3d0f3038621f1601f13539e7a9ee99843862e66ad62827b0c42b2f58a54"},
"certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"},
"comeonin": {:hex, :comeonin, "5.3.3", "2c564dac95a35650e9b6acfe6d2952083d8a08e4a89b93a481acb552b325892e", [:mix], [], "hexpm", "3e38c9c2cb080828116597ca8807bb482618a315bfafd98c90bc22a821cc84df"},
Expand All @@ -20,6 +21,7 @@
"ecto": {:hex, :ecto, "3.10.2", "6b887160281a61aa16843e47735b8a266caa437f80588c3ab80a8a960e6abe37", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "6a895778f0d7648a4b34b486af59a1c8009041fbdf2b17f1ac215eb829c60235"},
"ecto_psql_extras": {:hex, :ecto_psql_extras, "0.7.11", "6e20144c1446dcccfcdb4c142c9d8b7992a90a569b1d5958cbea5458550b25f0", [:mix], [{:ecto_sql, "~> 3.4", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.15.7 or ~> 0.16.0 or ~> 0.17.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "def61f1f92d4f40d51c80bbae2157212d6c0a459eb604be446e47369cbd40b23"},
"ecto_sql": {:hex, :ecto_sql, "3.10.1", "6ea6b3036a0b0ca94c2a02613fd9f742614b5cfe494c41af2e6571bb034dd94c", [:mix], [{:db_connection, "~> 2.5 or ~> 2.4.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f6a25bdbbd695f12c8171eaff0851fa4c8e72eec1e98c7364402dda9ce11c56b"},
"elixir_make": {:hex, :elixir_make, "0.7.8", "505026f266552ee5aabca0b9f9c229cbb496c689537c9f922f3eb5431157efc7", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "7a71945b913d37ea89b06966e1342c85cfe549b15e6d6d081e8081c493062c07"},
"eqrcode": {:hex, :eqrcode, "0.1.10", "6294fece9d68ad64eef1c3c92cf111cfd6469f4fbf230a2d4cc905a682178f3f", [:mix], [], "hexpm", "da30e373c36a0fd37ab6f58664b16029919896d6c45a68a95cc4d713e81076f1"},
"erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"},
"esbuild": {:hex, :esbuild, "0.7.0", "ce3afb13cd2c5fd63e13c0e2d0e0831487a97a7696cfa563707342bb825d122a", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "4ae9f4f237c5ebcb001390b8ada65a12fb2bb04f3fe3d1f1692b7a06fbfe8752"},
Expand All @@ -39,6 +41,7 @@
"html_sanitize_ex": {:hex, :html_sanitize_ex, "1.4.2", "c479398b6de798c03eb5d04a0a9a9159d73508f83f6590a00b8eacba3619cf4c", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm", "aef6c28585d06a9109ad591507e508854c5559561f950bbaea773900dd369b0e"},
"httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"},
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
"image": {:hex, :image, "0.42.0", "aa561f15b53c40ac571e7880083cecf1419ff405fc45dc95675c58aa308eaa22", [:mix], [{:bumblebee, "~> 0.3", [hex: :bumblebee, repo: "hexpm", optional: true]}, {:evision, "~> 0.1.33", [hex: :evision, repo: "hexpm", optional: true]}, {:exla, "~> 0.5", [hex: :exla, repo: "hexpm", optional: true]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: true]}, {:kino, "~> 0.7", [hex: :kino, repo: "hexpm", optional: true]}, {:nx, "~> 0.5", [hex: :nx, repo: "hexpm", optional: true]}, {:nx_image, "~> 0.1", [hex: :nx_image, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 2.1 or ~> 3.2 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: true]}, {:rustler, "> 0.0.0", [hex: :rustler, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: false]}, {:vix, "~> 0.23", [hex: :vix, repo: "hexpm", optional: false]}], "hexpm", "19972043abadc40e2d77dc38fc57f52382859791f89a962b0f1425ae64262f7d"},
"jason": {:hex, :jason, "1.4.0", "e855647bc964a44e2f67df589ccf49105ae039d4179db7f6271dfd3843dc27e6", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "79a3791085b2a0f743ca04cec0f7be26443738779d09302e01318f97bdb82121"},
"libcluster": {:hex, :libcluster, "3.3.3", "a4f17721a19004cfc4467268e17cff8b1f951befe428975dd4f6f7b84d927fe0", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "7c0a2275a0bb83c07acd17dab3c3bfb4897b145106750eeccc62d302e3bdfee5"},
"makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"},
Expand Down Expand Up @@ -111,6 +114,7 @@
"uri_query": {:hex, :uri_query, "0.1.2", "ae35b83b472f3568c2c159eee3f3ccf585375d8a94fb5382db1ea3589e75c3b4", [:mix], [], "hexpm", "e3bc81816c98502c36498b9b2f239b89c71ce5eadfff7ceb2d6c0a2e6ae2ea0c"},
"uuid": {:hex, :uuid, "1.1.8", "e22fc04499de0de3ed1116b770c7737779f226ceefa0badb3592e64d5cfb4eb9", [:mix], [], "hexpm", "c790593b4c3b601f5dc2378baae7efaf5b3d73c4c6456ba85759905be792f2ac"},
"versioce": {:hex, :versioce, "2.0.0", "a31b5e7b744d0d4a3694dd6fe4c0ee403e969631789e73cbd2a3367246404948", [:mix], [{:git_cli, "~> 0.3.0", [hex: :git_cli, repo: "hexpm", optional: true]}], "hexpm", "b2112ce621cd40fe23ad957a3dd82bccfdfa33c9a7f1e710a44b75ae772186cc"},
"vix": {:hex, :vix, "0.26.0", "027f10b6969b759318be84bd0bd8c88af877445e4e41cf96a0460392cea5399c", [:make, :mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:cc_precompiler, "~> 0.1.4 or ~> 0.2", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.7.3 or ~> 0.8", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:kino, "~> 0.7", [hex: :kino, repo: "hexpm", optional: true]}], "hexpm", "71b0a79ae7f199cacfc8e679b0e4ba25ee47dc02e182c5b9097efb29fbe14efd"},
"websock": {:hex, :websock, "0.5.2", "b3c08511d8d79ed2c2f589ff430bd1fe799bb389686dafce86d28801783d8351", [:mix], [], "hexpm", "925f5de22fca6813dfa980fb62fd542ec43a2d1a1f83d2caec907483fe66ff05"},
"websock_adapter": {:hex, :websock_adapter, "0.5.3", "4908718e42e4a548fc20e00e70848620a92f11f7a6add8cf0886c4232267498d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "cbe5b814c1f86b6ea002b52dd99f345aeecf1a1a6964e209d208fb404d930d3d"},
"xml_builder": {:hex, :xml_builder, "2.2.0", "cc5f1eeefcfcde6e90a9b77fb6c490a20bc1b856a7010ce6396f6da9719cbbab", [:mix], [], "hexpm", "9d66d52fb917565d358166a4314078d39ef04d552904de96f8e73f68f64a62c9"},
Expand Down
13 changes: 6 additions & 7 deletions test/banchan/thumbnailer_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,9 @@ defmodule Banchan.Workers.ThumbnailerTest do
user = user_fixture()

files = [
%{name: "test.bmp", type: "image/bmp"},
%{name: "test.gif", type: "image/gif"},
%{name: "test.ico", type: "image/vnd.microsoft.icon"},
%{name: "test.jpg", type: "image/jpg"},
%{name: "test.png", type: "image/png"},
%{name: "sample_640426.psd", type: "image/vnd.adobe.photoshop"},
%{name: "test.svg", type: "image/svg+xml"},
%{name: "test.svgz", type: "image/svg+xml"},
%{name: "test.tiff", type: "image/tiff"},
Expand Down Expand Up @@ -57,10 +54,12 @@ defmodule Banchan.Workers.ThumbnailerTest do
dimensions: "128x128"
)

assert %{width: 128, height: 128} =
Path.join([@upload_dir, thumbnail.bucket <> "/" <> thumbnail.key])
|> Mogrify.open()
|> Mogrify.verbose()
image =
Path.join([@upload_dir, thumbnail.bucket <> "/" <> thumbnail.key])
|> Image.open!()

assert 128 = Image.width(image)
assert 128 = Image.height(image)

Uploads.delete_upload(thumbnail)
Uploads.delete_upload(upload)
Expand Down
Binary file removed test/support/file-types/image/sample_640426.psd
Binary file not shown.
Binary file removed test/support/file-types/image/test.bmp
Binary file not shown.
Binary file removed test/support/file-types/image/test.heic
Binary file not shown.
Binary file removed test/support/file-types/image/test.ico
Binary file not shown.
Binary file removed test/support/file-types/image/test.xcf
Binary file not shown.

0 comments on commit 6b767dd

Please sign in to comment.