Image.write for WebP file, doesn't resize the images, unlike .pngs

Hey @kip,

After admiring your work on Image library for a long time, I finally got a chance to use it in my project.

I have a mix task, that compiles the images from assets/images/* directory and puts them into priv/images/* directory.

It creates different variations for input images.

defmodule Mix.Tasks.Images.Compile do
  @moduledoc "Compile all images of projects, inspired by Benjamin Milde's elixir blog."
  @shortdoc "Compiles images"
  use Mix.Task

  @assets_folder "assets/images/"
  @image_sizes [300, 720, 960, 1200, 2000]

  @impl Mix.Task
  def run(_args) do
    src_paths =
      (@assets_folder <> "**/*.{png,webp,jpeg,jpg}")
      |> Path.wildcard()
      |> dbg()

    for src_path <- src_paths, size <- @image_sizes do
      file_name = src_path |> Path.rootname() |> Path.basename()
      ext = src_path |> Path.extname()

      path = src_path |> Path.split() |> Enum.drop(1) |> Path.join()
      dest_dir = "priv/static" |> Path.join(path) |> Path.dirname()
      dest_path = "#{dest_dir}/#{file_name}@#{size}#{ext}"

      {:ok, thumb} = Image.thumbnail(src_path, size)

      File.mkdir_p!(dest_dir)

      Image.write(thumb, dest_path,
        quality: 70,
        strip_metadata: true,
        minimize_file_size: true,
        compression: 6
      )
    end
  end
end

This produces following output: (For PNGs)

However, when I run the same code on WebP, it doesn’t work! Filename, may have changed, but the sizes remain the same.


What is your thoughts on tools like imgproxy?

After resize was done, I was thinking pre-compiling led to unnecessary resizing. For instance for images that are smaller than 500px, was being stretched to 1200px needlessly.

I will have to segregated folders and selectively resize them to different sizes, instead of resizing at runtime like imageproxy does.

In most cases I prefer it over dealing with it on upload or whatever. Just slap some kind of cache in front of it to remove the need to duplicate work already done and you are good to go.

3 Likes

I experimented with imgproxy & varnish cache, and I loved it.

I was just trying to learn Image library and how to resize, compress programmatically.

I will find other use for the library though.

Do you have a sample of the image and sizes you are using? I have two projects on the go atm that create webp thumbnails for and have had no issue.

One thing you can try is removing some of the options you’re passing. Not all options make sense for all filetypes so there is an effort to generalize them. For instance at one point setting a quality of 100 for PNG would cause a crash (it has now been fixed). Maybe try removing some options and see if that fixes it and if so, you will have found a bug!

1 Like

@sodapopcan,

I played around with the options, and I went through the documentation to find those values.

The original images are in 724 Ă— 663 size, with .png & .webp variations.

I couldn’t make the code work for webp.

I just tried out your script with a dbg on Image.write and I got:

"Invalid option or option value: {:compression, 6} for image type \".webp\""}

Appears as if you have indeed found a bug :slightly_smiling_face:

1 Like

@sodapopcan, :compression isn’t a valid option for .webp images. I checked the documentation and I can’t see a reference to it as an option so it’s just an example where different image types support different parameters for writing and therefore a generic writer is a bit tricky. Its why I added :minimise_file_size.

image returns an error on known but invalid options for a given image type so that there is no mysterious behaviour. Open to a discussion if that’s the right choice.

2 Likes

I am working (slowly) on a similar capability in a separate library. I’m currently working on:

  • image_plug to provide a basic plug for serving images using a URL format similar to imgproxy.
  • image_cache to provide a caching layer over the top of image_plug.
  • image_components to provide a live view image component.
  • color to provide a standalone color library that image will then use.

I can’t estimate when they’ll be done but I will get them done.

That’s very strange! If you’re able to provide me an image example I’ll very happily take a look at it.

4 Likes

I was thinking of it as a “bug” as in I thought the desired behaviour would be for Image.write to ignore invalid options for certain file types that were valid for others—sort of how you massaged quality to work across types when we came across the quality thing for pngs a few weeks ago. It makes sense not to do it that way, too, I just wasn’t sure!

@derpycoder I just fixed a bug whereby :minimize_file_size wasn’t correctly applying :"min-size" option when writing a .webp image. That might help resolve the .webp image file size issue. I’ve not published an update to hex yet, I want to do some more testing with the updated evision that was just published first.

1 Like

It’s also a reasonable choice. How about emit a log message (at debug level) in such cases so expectations are set correctly? I’m a bit uncomfortable about the idea of someone setting :compression and expecting a file size reduction. Given reducing file size is a common objective, messing with that expectation wouldn’t be a very good experience.

1 Like

I agree—I honestly didn’t re-check the docs this time, I just had an inkling that @derpycoder’s problems was stemming from options as I remember something similar happening to me. I just remember you and I briefly touching on being able to pass generic options which would make this very usecase of creating different formats in a loop more convenient, but I honestly don’t remember very well. Maybe I made that up :sweat_smile: I do agree that it’s not great DX to have a no-op option and would be best for library users to manage passing the correct ones.

Definitely agree, that’s why the :minimize_file_size option exists. But I messed the underlying options for .webp (now fixed). That’s the most generic “do the best you can” option to minimise the size of an image file.

2 Likes

Is it possible for the configuration to be nested, so I can pass the whole config in one go, instead of switching the config ourselves?

Like

%{
png: %{compress: 60, lossy: true},
jpg: %{quality: 70},
webp: %{quality: 5}
}

I have pushed a commit that does what you proposed, but in keyword list format. For example:

iex>  Image.write(image, image_path, minimize_file_size: true,
...>    png: [compress: 60, lossy: true],
...>    jpg: [quality: 70],
...>    webp: [quality: 5])

I need to add tests before publishing but you’re welcome to try it out by configuring image from GitHub.

Since this is now getting very specific I suggest followup questions/discussion on this topic move to image discussions on github.

4 Likes

I have done something similar in the past with Imager. It should be simple enough to replace single module to make both of these