Trying to find how to do multiple write operations with a loaded image #51
Replies: 9 comments
-
This is probably my fault. TLDR; use As for the order of operations, There is one "special case" which is
|
Beta Was this translation helpful? Give feedback.
-
In short, your example (which I copied below) is probably the most efficient for this. But it would need to be benchmarked against using the pathname form of def open_and_thumbnail_random_access() do
@img_path
|> Image.open!(access: :random)
|> Image.thumbnail!("1920x1080")
|> convert_image_in_multiple_formats()
end I'll fire up benchee and see what it says. |
Beta Was this translation helpful? Give feedback.
-
I added a simple benchmark with the following results that suggests the approach you are using (and which I documented again in the message above) is probably the fastest way to do what you're after. As I understand it, the most expensive operation is decoding the image and therefore not too surprising if limiting the number of times an image is opened is a good thing. Name ips average deviation median 99th %
Image.open |> Image.thumbnail 9.06 110.37 ms ±2.55% 109.82 ms 121.92 ms
Image.thumbnail(path) 6.24 160.22 ms ±1.07% 159.92 ms 165.08 ms
Comparison:
Image.open |> Image.thumbnail 9.06
Image.thumbnail(path) 6.24 - 1.45x slower +49.84 ms
Memory usage statistics:
Name Memory usage
Image.open |> Image.thumbnail 10.57 KB
Image.thumbnail(path) 19.08 KB - 1.80x memory usage +8.51 KB |
Beta Was this translation helpful? Give feedback.
-
I will change the default to |
Beta Was this translation helpful? Give feedback.
-
Thanks a lot for taking the time to answer. It is a lot clearer now. I had planned to investigate the shrink-on-load option. I was wondering why I've reused your benchmark and changed a few things, especially the input file by using a larger file such as the Hong-Kong one. image_path = Path.expand("test/support/images/Hong-Kong-2015-07-1998.jpg") # 8356x4700
dims = "500x200" # x2 = 1000x400
# 8356x4700 / 8 = 1044x587
open_and_thumbnail =
fn shrink ->
image =
image_path
|> Image.open!(shrink: shrink)
|> Image.thumbnail!(dims)
Image.write!(image, :memory, suffix: ".jpg", quality: 40, strip_metadata: true)
Image.write!(image, :memory, suffix: ".avif", effort: 1, quality: 40, strip_metadata: true)
Image.write!(image, :memory, suffix: ".webp", quality: 30, strip_metadata: true)
end
Benchee.run(
%{
"Image.open |> Image.thumbnail (no shrink)" => fn -> open_and_thumbnail.(1) end,
"Image.open |> Image.thumbnail (shrink of 2)" => fn -> open_and_thumbnail.(2) end,
"Image.open |> Image.thumbnail (shrink of 4)" => fn -> open_and_thumbnail.(4) end,
"Image.open |> Image.thumbnail (shrink of 8)" => fn -> open_and_thumbnail.(8) end,
"Image.thumbnail(path)" => fn ->
Image.thumbnail!(image_path, dims) |> Image.write!(:memory, suffix: ".jpg", quality: 40, strip_metadata: true)
Image.thumbnail!(image_path, dims) |> Image.write!(:memory, suffix: ".avif", effort: 1, quality: 40, strip_metadata: true)
Image.thumbnail!(image_path, dims) |> Image.write!(:memory, suffix: ".webp", quality: 30, strip_metadata: true)
end
},
time: 20,
memory_time: 2
) On a large file (which tend to be common these days with smartphones oftenly creating files of several megabytes), and without shrink, it seems that the Image.open |> Image.thumbnail (shrink of 8) 16.86 59.32 ms ±5.08% 59.27 ms 67.93 ms
Image.open |> Image.thumbnail (shrink of 4) 2.37 422.28 ms ±9.14% 416.99 ms 506.17 ms
Image.thumbnail(path) 1.61 622.58 ms ±1.92% 619.72 ms 686.25 ms
Image.open |> Image.thumbnail (shrink of 2) 1.20 830.67 ms ±9.22% 843.32 ms 982.09 ms
Image.open |> Image.thumbnail (no shrink) 0.42 2404.69 ms ±6.72% 2360.68 ms 2638.81 ms
Comparison:
Image.open |> Image.thumbnail (shrink of 8) 16.86
Image.open |> Image.thumbnail (shrink of 4) 2.37 - 7.12x slower +362.96 ms
Image.thumbnail(path) 1.61 - 10.50x slower +563.26 ms
Image.open |> Image.thumbnail (shrink of 2) 1.20 - 14.00x slower +771.35 ms
Image.open |> Image.thumbnail (no shrink) 0.42 - 40.54x slower +2345.37 ms
Memory usage statistics:
Name Memory usage
Image.open |> Image.thumbnail (shrink of 8) 31.66 KB
Image.open |> Image.thumbnail (shrink of 4) 31.66 KB - 1.00x memory usage +0 KB
Image.thumbnail(path) 28.99 KB - 0.92x memory usage -2.66406 KB
Image.open |> Image.thumbnail (shrink of 2) 31.66 KB - 1.00x memory usage +0 KB
Image.open |> Image.thumbnail (no shrink) 31.66 KB - 1.00x memory usage +0 KB |
Beta Was this translation helpful? Give feedback.
-
Wow, this is great work @quentin-bettoum, really appreciate it and your results are close to what I expected earlier.
Correct,
That is why, in general, using Lastly, see how little memory is used? Its one of the really great things about the on-demand streaming nature of |
Beta Was this translation helpful? Give feedback.
-
Hmmm, I wonder if there is a way to estimate what the optimal shrink-on-load value might be for a given image and target thumbnail size? |
Beta Was this translation helpful? Give feedback.
-
Yes, and I was thinking it might not be relevant, because if I use A few exemples with the Hong-Kong photo: # Using thumbnail to output a 500x200
vips thumbnail Hong-Kong-2015-07-1998.jpg hong-kong.webp[Q=30,strip] 500 --height=200
memory: high-water mark 8,53 MB Without thumbnailing: vips webpsave Hong-Kong-2015-07-1998.jpg hong-kong.webp
memory: high-water mark 45,90 MB
# Its interesting to see that the `copy` will take a little bit more memory to do the same thing as `webpsave` above.
vips copy Hong-Kong-2015-07-1998.jpg hong-kong.webp
memory: high-water mark 47,43 MB I've done these tests with some smaller images than the Hong-Kong one, and the memory high-water mark drops significantly. In the Benchee doc about Measuring Memory Consumption, it is said:
So I'm guessing that the external libvips processes aren't taken into account either.
That's what I will look for now, I will report back if I find a solution. |
Beta Was this translation helpful? Give feedback.
-
Oh, indeed, very good point. As for memory high water mark, you can get that same output with export VIPS_LEAK=true It will output to stdout when you exit the application ( |
Beta Was this translation helpful? Give feedback.
-
I've been playing around with Image and Vix and this is what I'm trying to do:
I guess it would be more optimized in this order since the conversion would be done on a downsized version of the input image.
It seems there are different ways to do this, but most of the time I have a
Failed to write VipsImage
error when trying to do multiple write operations to different files, it will write the first one and fail for all other attempts.I obviously lack experience about file handling in general. I'm reading docs and articles about Elixir files handling, File.stream, etc.
Here is a module I've made for my tests, I've added some comments to indicate where it succeeds and where it fails.
I've tried using both
Image
andVix
methods. It seems usingImage.write!/3
orVix.Vips.Image.write_to_file/2
gives the same result.I would appreciate any help to understand what are the reasons of the errors.
Beta Was this translation helpful? Give feedback.
All reactions