Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Dynamic Image Resizing #99

Open
3 tasks
nelsonic opened this issue Jul 18, 2023 · 16 comments
Open
3 tasks

Feat: Dynamic Image Resizing #99

nelsonic opened this issue Jul 18, 2023 · 16 comments
Labels
discuss Share your constructive thoughts on how to make progress with this issue documentation Improvements or additions to documentation enhancement New feature or enhancement of existing functionality help wanted If you can help make progress with this issue, please comment! priority-2 Second highest priority, should be worked on as soon as the Priority-1 issues are finished T4h Time Estimate 4 Hours technical A technical issue that requires understanding of the code, infrastructure or dependencies

Comments

@nelsonic
Copy link
Member

At present we have single size image resizing: 7. Resizing/compressing files
This is a very good starting point, 👍
but it sets and artificial constraint
that ends up looking "meh" on most devices.

ginger-compressed

image

Story

As a person using multiple devices to view the App - that features image content -
I want images to conform to the device/screen size
So that they always look their best.

As noted in #91 our friend has created https://github.com/jupiter/rust-image-worker
which appears to be well-documented and tested and runs on Cloudflare with CDN caching. 🏎️
While it currently does not have support for certain formats jupiter/rust-image-worker#3
But the underlying library does support GIF and Webp: https://docs.rs/image/latest/image/enum.ImageFormat.html#variant.Gif

Todo

@nelsonic nelsonic added enhancement New feature or enhancement of existing functionality help wanted If you can help make progress with this issue, please comment! T4h Time Estimate 4 Hours discuss Share your constructive thoughts on how to make progress with this issue priority-2 Second highest priority, should be worked on as soon as the Priority-1 issues are finished technical A technical issue that requires understanding of the code, infrastructure or dependencies documentation Improvements or additions to documentation labels Jul 18, 2023
@nelsonic nelsonic pinned this issue Jul 18, 2023
@ndrean
Copy link

ndrean commented Aug 24, 2023

@nelsonic I am curious. ,You must probably have read this article. Since (obviously) you are using a server to upload image, what is the reason you did not consider to run your own Task to compress the image before saving to S3? You would save a bucket and a lambda. Something obvious I am missing?

@nelsonic
Copy link
Member Author

@ndrean hadn't seen that fly.io blog post published Mar 13, 2023; thanks for sharing.
image
image
image

It's quite superficial, not really a "tutorial", more of a "you can do this thing"
but you have figure out the precise details on your own ... 🙃
That's exactly the kind of post that mega frustrates me.

To answer your question:
we want to maintain the original image so that people can view as much detail as possible.
We will save money when we switch to using B2 #98
I don't have anything against optimising the images in a Task in Elixir.
But I would prefer to minimise the Elixir bottleneck and use an existing Rust project to the just-in-time optimisation.
From an experience perspective the Elixir approach where you perform the immediate resizing could be better in terms of response time. ⏳
We could test it.
For now the S3 buckets + Lambda function works "OK".
Have you implemented image compression/resizing in Elixir? how fast was it?

Ultimately using B2 + Cloudflare which is "free" for their CDN would save us money (over S3 + Lambda)

@ndrean
Copy link

ndrean commented Aug 24, 2023

@nelsonic, Yes superficial indeed, but it gives ideas.

But aren't you serving the client back with a compressed image?
https://user-images.githubusercontent.com/17494745/242736344-bd61d716-8a4e-445f-a643-8f5d13a00510.png

that's why I imagined it was an extra step (and I always fear AWS costs....).

I forked the repo but did not run it yet but I plan to as I never did this server-side nor loaded files yet with Phoenix. But I have no intention so far to send something to S3, too much bills with AWS.

I maybe totally wrong but I thought a simple URL.createObjectURL for the preview was enough, although I have no idea if Liveview accepts this.

I will also try the https://hexdocs.pm/image/Image.html and explore

@nelsonic
Copy link
Member Author

Yeah, the current AWS bill is currently Zero because it's all in the "Free Tier".
Which is why the priority on #98 is priority-2
Returning the compressed image URL is a stop-gap and will be phased out in due course
instead the device will request the size of image that best fits the viewport #91
therefore prospectively resizing becomes wasteful.
If the device uploading the image is 600px wide there's no need to have images 200px or 2000px wide.
Then when subsequent devices attempt to view the image, if they can request dimensions that best suit their viewport without a significant latency penalty, that's super desirable.

Very curious to see your exploration of the https://github.com/elixir-image/image library. 👌
I didn't find the docs to be particularly beginner-friendly when I tried to read them a few months ago ... 🤷‍♂️

I'm especially not a huge fan of risk of crashing the whole BEAM with one malformed image:
https://hexdocs.pm/image/readme.html#security-considerations
image

If I can offload image processing/resizing to a totally separate Rust micro-app I'm going to chose that every time. 🚀
But if I needed to do Image recognition in Elixir then the image library looks interesting. 💭

@ndrean
Copy link

ndrean commented Aug 26, 2023

@nelsonic I am exploring 2 ideas. If this does not interest you, just tell me (nicely 😄). I send the image from the browser (not from PHoenix) to a (tiny) companion Node.js server that runs sharp,. The server returns a resized image based on the device. You then PUT directly to S3 via an API Gateway, no round trip to the PHoenix server, and the bucket is closed, except this endpoint that exposes him, of course.

  1. The Node.js server (30 LOC) runs independently next to the Phoenix app. It includes sharp. You define an endpoint to which the Phoenix app can POST the image as well as the desired dimensions you collect from the device (eg window.innerHeight * 0.5). You resize the image accordingly. On completion, it will respond back. I am fighting with how to display a preview of the result back.

  2. It seems that sending the image to Phoenix is consuming resources but you don't perform anything on the image. You can send the image directly to S3 via an API Gateway, no round trip. The whole thing takes a few LOC (including the "CIDing of the filename and saving the metadata of the file to the Phoenix database). I am fighting with a CORS problem though. If you are interested, I can append some code here.

@nelsonic
Copy link
Member Author

@ndrean these are all perfectly valid suggestions and we have done something similar in the past.

our goal with doing this in Phoenix is to minimise latency. In the best case scenario API Gateway + Lambda is about 300ms round trip. Much worse if the Lambda function has to “cold” boot 🥶
Direct Upload to S3 works and we’ve done that before too.

Perhaps the most important thing we aren’t yet doing in the imgup app is logging metadata and person info so that the people making the uploads can see all their images easily.
in other words, try to think of where an image uploading service with privacy focus can go rather than just looking at what we currently already have.
to be clear: I agree that we don’t need Phoenix. Heck we could achieve what we currently have with a single PHP file/script copy-pasted from StackOverflow or ChatGPT …
But when it comes to a roadmap that heavily features images, having a clear grasp of the stack is mega important.

@ndrean
Copy link

ndrean commented Aug 27, 2023

@nelsonic Of course, I do not pretend any originality at all. Just that I found the Phoenix code difficult. When you say "300ms round trip", you mean between upload and the display of the URL and preview of their pics?

Perhaps the most important thing we aren’t yet doing in the imgup app is logging metadata and person info so that the people making the uploads can see all their images easily.

I probably misunderstood. You mean a profile to be able to join his URLs and meta, thus retrieve?

@nelsonic
Copy link
Member Author

Yeah, for now this repo is “just” a simple way to upload images. If we had time we would build out the rest of the features. 💭

@ndrean
Copy link

ndrean commented Aug 28, 2023

I imagine you want to use the "standard" mix .phx.gen.auth and a join table of S3 URLs. The tricky part is probably to handle safely thousands of downloads S3 -> client.

@nelsonic
Copy link
Member Author

Indeed. The “standard”, though woefully incomplete, mix phx.gen.auth is what we are using as the basis for our auth re-write which is ongoing … 👌
Then imgup will have one-tap auth and images will be associated with a person_id so people can easily see & share the images they’ve uploaded. ✅

@ndrean
Copy link

ndrean commented Aug 29, 2023

You probably know https://imagekit.io/ ? I found interesting the way they did it, and rather inline with your roadmap,

Watch https://www.youtube.com/watch?v=sWcSYG1eifo
Screenshot 2023-08-29 at 17 18 50

however, the price seems high.
Screenshot 2023-08-29 at 17 25 16

  • the idea of having a default S3 bucket offered, and then using your own storage, db or your own S3 bucket
  • the idea of having a separate transformation process, and using the url for specific transforms.

@ndrean
Copy link

ndrean commented Aug 30, 2023

@nelsonic I believe I have a use case for a modal in the following situation: once you uploaded files, you have a list or miniatures previews of them. You can put a modal for each file to 1) link to a new page 2) a form to enter a name and download the file. What do you think?

@nelsonic
Copy link
Member Author

Respectfully, I disagree. 🙅
Unless you need to hijack the person's attention for very specific reason,
Modals are never the answer to "How do we make excellent UX here?"
If you've ever watched a senior citizen use the web they are always confused by them.

Interacting with a bank of images needs to be as "flat" as possible. Inserting a new image into the DOM and applying a highlight (border) around it is enough to show the recently uploaded file. Opening the larger version of that file should be full-screen with a gentle transition and a "back button" to allow them to return to the list.

Apologies if I come across as "harsh" (bordering on militant) but Modals are evil and should be avoided at all costs. 😉

@ndrean
Copy link

ndrean commented Sep 3, 2023

I used Vix to compress/resize (Vix.Vips.Image and Vix.Vips.Operation). It is easy and super fast.. You can do this in the clientless flow in the handle_progress

A JPEG 5472x3648 of 2.5MB
action

compressed to 988kB (Q=30):
small

resized 10%: to 547x365 (43kB):
small

and a thumb of 5kB with the same ratio 1.5
thumb

@ndrean
Copy link

ndrean commented Sep 3, 2023

A guide: https://imagekit.io/blog/how-to-resize-image-in-html/

Screenshot 2023-09-03 at 12 34 11

@ndrean
Copy link

ndrean commented Sep 6, 2023

@nelsonic

Let me know what you think of this? it is only SSR because Phoenix transforms the images.

When it reads a picture, I transform it into WEBP format to minimise the size. Only WEBP fomat are saved in S3.

You can let the browser resize the pic for you, or minimise the data over the wire and resize it. Resize to what? To the uploading device for example?

With this 100% Phoenix strategy, we could run a job to compute the resizing for different pre-defined devices, upload this to S3. You will have several versions of the same pic in S3. Then - upon some matching - when you want to display a picture, you "fetch" the right format depending upon your device.

To keep it simple - and given I don't want to pay for - , if you upload a pic from a device, the image will be resized to the dimension of the device meaning that the S3 upload is adapted to your device. I do not keep the original format, just because this is a demo, but it is possible.

https://up-image.fly.dev

This kind of app - with the jungle of the async messaging I used - could benefit of the new assign_async coming
in LiveView 0.20: see https://www.youtube.com/watch?v=FADQAnq0RpA

I tried to use it when you upload several files. Each upload will trigger a "writter" and then you invoke a start_async and handle_async. This works for one upload. Indeed, each task is identified by its own unique key. If you upload several files, this will be triggered several times and invoke the same key for this async task, so this will fail. In such case, a "traditionnal" Task with the handle_info handlers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
discuss Share your constructive thoughts on how to make progress with this issue documentation Improvements or additions to documentation enhancement New feature or enhancement of existing functionality help wanted If you can help make progress with this issue, please comment! priority-2 Second highest priority, should be worked on as soon as the Priority-1 issues are finished T4h Time Estimate 4 Hours technical A technical issue that requires understanding of the code, infrastructure or dependencies
Projects
Status: 🔖 Ready for Development
Development

No branches or pull requests

2 participants