Image - an image processing library based upon Vix

I’m trying to create an image thumbnail but I’m getting an error from some images, not all of them. I’m using LiveView, the uploaded image comes without an extension, so I add it to the filename.

defmodule MyApp.Helpers.FileManager do
  def upload_image(%{path: path}, entry) do
    path_with_ext = path <> Path.extname(entry.client_name)
    File.rename(path, path_with_ext)
    filename = Path.basename(path_with_ext)

    destination= Path.join("priv/static/images", filename)

    path_with_ext
    |> Image.open!()
    |> Image.thumbnail!("256x256")
    |> Image.write(destination)

    {:ok, "/images/#{filename}"}
  end

Here’s the code from the component mentioned in the error

def params_with_image(socket, params) do
    path =
      socket
      |> consume_uploaded_entries(:image, &FileManager.upload_image/2)
      |> List.first()

    Map.put(params, "image", %{path: path})
  end

Error, as I said, it happens only on some images, not all of them. It’s interesting that it’s happening always on the same ones, although they are all .jpg.

[error] GenServer #PID<0.1361.0> terminating
** (stop) exited in: GenServer.call(#PID<0.1372.0>, :consume_done, :infinity)
    ** (EXIT) no process: the process is not alive or there's no process currently associated with the given name, possibly because its application isn't started
    (elixir 1.16.0) lib/gen_server.ex:1114: GenServer.call/3
    (phoenix_live_view 0.20.3) lib/phoenix_live_view/upload_channel.ex:26: Phoenix.LiveView.UploadChannel.consume/3
    (elixir 1.16.0) lib/enum.ex:1700: Enum."-map/2-lists^map/1-1-"/2
    (myapp 0.1.0) lib/myapp_web/live/account_live/profile_form_component.ex:94: MyAppWeb.AccountLive.ProfileFormComponent.params_with_image/2
    (myapp 0.1.0) lib/myapp_web/live/account_live/profile_form_component.ex:75: MyAppWeb.AccountLive.ProfileFormComponent.handle_event/3
    (phoenix_live_view 0.20.3) lib/phoenix_live_view/channel.ex:719: anonymous fn/4 in Phoenix.LiveView.Channel.inner_component_handle_event/4
    (telemetry 1.2.1) /home/matija/Sites/myapp/deps/telemetry/src/telemetry.erl:321: :telemetry.span/3
    (phoenix_live_view 0.20.3) lib/phoenix_live_view/diff.ex:209: Phoenix.LiveView.Diff.write_component/4
    (phoenix_live_view 0.20.3) lib/phoenix_live_view/channel.ex:651: Phoenix.LiveView.Channel.component_handle/4
    (stdlib 5.2) gen_server.erl:1095: :gen_server.try_handle_info/3
    (stdlib 5.2) gen_server.erl:1183: :gen_server.handle_msg/6
    (stdlib 5.2) proc_lib.erl:251: :proc_lib.wake_up/3

@kip can you point me in the right direction?

I would refactor this a bit to be:

with {:ok, thumbnail} <- Image.thumbnail(path_with_ext, 256),
     {:ok, _} <- Image.write(thumbnail, destination) do
  {:ok, “image/#[filename}”}
end

At least then you can see if the imaging pipeline is the source of the error (the stacktrace isn’t much help in this case).

Secondly, assuming you have one of the failing images, simply try thumbnailng it in iex and see if you get an error return with a useful stacktrace. And of course, feel free to open an issue and attach a failing image - happy to take a look at it.

Next, Image.thumbnail/3 will resize the image so the longest edge meets the supplied domension. It won’t change the aspect ration of the image unless you use the resize: :force option. So in your case, just 256 as the size parameter should do the trick.

Lastly, libvips has some optimisations for thumbnailng if you thumbnail directly from the path. It can combine file opening and block reductions so it’s highly recommended doing that when you can.

1 Like

I’ve tried it in iex and everything works just fine. The file was always successfully created even before, but it crashes right after it. I suspect it’s related to LiveView Upload somehow. Thanks for your help, I’ll try to identify what’s causing it.

Edit: using Image.thumbnail/3 without Image.open() and Image.write() fixed the issue.

I’ve just published Image version 0.43.0. It has some breaking changes (rename functions in nearly all cases). This brings the code a lot close to a 1.0 release. The remaining issue is to complete the re-write of the color handling code over the next couple of months.

Image version 0.43.0 changelog

Breaking Changes

  • Image.erode/2 and Image.dilate/2 now take a radius parameter rather than a pixels parameter. Both functions have been refactored to allow a radius in the range 1..5 with a default of 1. The radius represents the dimension of the matrix used in the Vix.Vips.Operations.range/4 function that underpins dilation and erosion. As such they represent the approximate number of pixels eroded or dilated. In addition, this function now results in a single libvips operation. The previous implementation created n operations (where n was the value of the pixels param) that could result in a slow imaging pipeline and in some cases a segfault of the entire VM due to stack space exhaustion in libvips.

  • The signature for Image.linear_gradient/{1..3} has changed. The function now takes:

    • An image and an optional keyword list of options
    • A width and height as numbers and a keyword list of options
  • Image.dominant_color/2 now returns an {:ok, rgb_color} tuple rather than a [r, g, b] list. Use Image.dominant_color!/2 if only the color value return is required.

  • Image.map_pages/2 is deprecated in favour of Image.map_join_pages/2 to better reflect the intent of the function.

Enhancements

  • Image.linear_gradient/{1..3} now takes an :angle option which determines the angle of the gradient in degrees. Thanks to @severian1778 for considerable patience. Closes #67.

  • Improve options handling and documentation for Image.radial_gradient/3.

  • Add Image.radial_gradient!/3 to mirror Image.radial_gradient/3.

  • Add Image.dominant_color!/2 to mirror Image.dominant_color/2.

  • Add Image.extract_pages/1 which will extract the pages of a multi-page image into a list of separate images.

9 Likes

I’ve published Image version 0.45.0. Please note this includes a breaking change for how images from text are rendered. For most (maybe all?) current use cases the change may not be visible. Since I didn’t post an announcement for Image 0.44.0 I’m including that changelog here now too.

Breaking changes

The implementations of Image.text/2 and Image.simple_text/2 have been simplified to use only the built-in Pango renderer. A bug in font sizing using the Pango renderer has also been fixed. As a result, there may be some small visual differences between text images generated by Image 0.45.0 compared to previous releases.

  • Image.text/2 now uses only the built-in Pango renderer for all use cases. SVG is not nhow used for any rendering in Image.text/2 or Image.simple_text/2. This gives a more consistent output and less ambiguity. However as a result, a small number of options are no longer available since they cannot be honoured by Pango:

    • :text_stroke_color

    • :text_stroke_width

  • The :autofit option to Image.text/2 is also removed. The autofit capability is now controlled by whether the :width and/or :height options are provided.

  • Some other options are now treated differently in Image.text/2:

    • :width and :height are now truly optional. If ommitted, the renderer will calculate the required image size based upon the other options. It is acceptable to specify :width and omit :height in which case the maximum width is fixed and the height is variable.

Bug Fixes

  • Fix warnings on upcoming Elixir 1.17.

  • A bug resulting in incorrect font sizing with using the Pango renderer has been fixed. Font sizing is now very similar to the sizing of the previously used SVG renderer.

Enhancements (Image 0.44.0)

  • Adds Image.Blurhash.encode/2 and Image.Blurhash.decode/1 to encode and decode blurhashes. Based upon a fork of rinpatch_blurhash. Thanks to @stiang for the suggestion. Thanks very much to @rinpatch for the implementation.
7 Likes

Blurhash is a really nice addition!

1 Like