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
Big ticket about Image handling #2866
Comments
Thank you @Levdbas! I'll post this issue I wrote some years ago and never posted. While reading it again, I feel that it's still relevant and can provide a starter base to discuss future Timber image handling. It's been a long time that I wanted to share my thoughts on image manipulation. Indeed, images handling has changed over the last years. Developers work sometimes very hard to win a few bytes on other assets (js, css, etc.) while images have an incredible reduction weight and performance gain potential. The ideal Twig image manipulation APIHere's my insights on an image manipulation API.
So I came up building my own image manipulation library. It was built upon spatie/image which stands on thephpleague/glide which itself uses Intervention/image. Yes, I know that's a lot of dependencies but those are pretty solid ones and The twig filter can be used by chaining methods: {{ img.file|width(300)|brightness(30)|blur(5)|to('webp') }} or as an array: {{ img.file|manipulate({
width: 300,
brightness: 30,
blur: 5,
format: 'webp'
}) }} or both at the same time: {{ img.file|manipulate({
width: 300,
brightness: 30,
blur: 5,
})|to('webp') }} You can generate an srcset by specifying a width range: {{ img.file|manipulate({
widths: {
min: 300,
max: 1200,
step: 100,
}
}) }}
// or
{{ img.file|widths({min: 300, max: 1200}) }} Or defined sizes: {{ img.file|widths([300, 768, 1024]) }} A "scaler" will generate an image every 100px (the step can be changed in the image factory instanciation or overriden at image level). So the output will look like: <img srcset="image.jpg 300w, image.jpg 400w, image.jpg 500w, image.jpg 600w, image.jpg 700w, image.jpg 800w, image.jpg 900w, image.jpg 1000w, image.jpg 1200w"
/> You can optimize an image (if your server have the necessary binaries): {{ img.file|width(300)|optimize }} Optimization can also be set as Here's what it looks like in practice: {% set operations = {
width: 600,
height: 600,
crop: 'crop-center',
srcset: [300, 800],
} %}
{% set operations_lqip = {
width: 30,
height: 30,
crop: 'crop-center',
blur: 10
} %}
<picture>
<source
data-srcset="{{ img.file|manipulate(operations)|to('webp') }}"
type="image/webp"
>
<img
src="{{ img.file|manipulate(operations_lqip)|datauri }}"
data-srcset="{{ img.file|manipulate(operations) }}"
class="lazyload"
/>
</picture> More abstraction can be done for multiple format generation, I'm currently using this: {{ picture(
img.file,
{
widths: {
min: 300,
max: 700,
},
width: 600,
height: 600,
crop: 'crop-center',
},
{
alt: img.alt|default(post.title),
class: 'mix-blend-multiply',
width: 600,
height: 600,
},
['webp', 'avif]
) }} Will ouput: <picture>
<source
srcset="image.avif 300w, image.avif 400w, image.avif 500w, image.avif 600w, image.avif 700w, image.avif 800w"
type="image/avif"
>
<source
srcset="image.webp 300w, image.webp 400w, image.webp 500w, image.webp 600w, image.webp 700w, image.webp 800w"
type="image/webp"
>
<img
src="image.jpg"
srcset="image.jpg 300w, image.jpg 400w, image.jpg 500w, image.jpg 600w, image.jpg 700w, image.jpg 800w"
class="mix-blend-multiply"
width="600"
height="600"
/>
</picture> Here is the signature: {{ picture(
realtive or absolute path to image,
{
manipulations
},
{
attributes
},
[
formats
]
) }} Bottom linesManipulations can be very greedy depending of many factors (number of images generated, number of manipulations applied, source file size, server capabilities, etc.), espacially if you generate a lot of sizes for your {% set operations = {
width: 600,
height: 600,
crop: 'crop-center',
widths: {
min: 300,
max: 1100,
step: 100,
},
} %}
<picture>
<source
data-srcset="{{ img.file|manipulate(operations)|to('webp') }}"
type="image/webp"
>
<img
data-srcset="{{ img.file|manipulate(operations) }}"
class="lazyload"
/>
</picture> With a step set to 100px, the example above will generate 10 images per format. Which leads to 20 images, for one displayed image 😱. As a workaround, I introduced a kind of throttle mecanism which only generate 3 images per manipulation group per load (configurable), starting with the bigger ones. So on the first load: srcset="image.webp 900w, image.webp 1000w, image.webp 1100w" On the second load: srcset="image.webp 600w, image.webp 700w, image.webp 800w, image.webp 900w, image.webp 1000w, image.webp 1100w" On the third load: srcset="image.webp 300w, image.webp 400w, image.webp 500w, image.webp 600w, image.webp 700w, image.webp 800w, image.webp 900w, image.webp 1000w, image.webp 1100w" The downside is that if you make use of static cache, the first loaded version will be cached and the set of images will only be generated next time the cache expires. |
Hi @nlemoine , You went all in with this! Love the ideas!
Totally agree
Yes please!
For me, this would be the biggest win personally.
I totally agree with you on this one if it would be easy to load that image fluently from that directory.
Mostly agree with you here, but maybe there should be a specific order in which manipulations are processed. With the chaining method, not sure this could case issues? In the end we want to make it as easy as possible for the end user. Not sure if two methods that do the same are the way to go there?
This is simply amazing!
We should just provide an action here were you can insert your own caching clearing method of choice. |
The proposal from @nlemoine is very much on point and I think just what we need in Timber. Five years ago, I also tried to summarize some of the issues that came up for image manipulation: I think the solution proposed by @nlemoine could solve a lot of problems developers ran into. I don’t think we have to go through all the issues again and see if we can solve them. I just want to list them here to give an overview and to make the link to the discussions from 2018. Changing qualityWe should be able to change the quality of images, either for all images or per instance.
Upscaling and RetinaWe should be able to control if an image should be upscaled or not.
Better control of resizing process
Feature parity with WordPress
|
Originally posted by @gchtr in #2876 (comment) |
As discussed recently with the Timber core team, we have come to the conclusion that a lot of tickets/feature requests are about how we handle image in Timber. This ticket will serve as an archive for multiple tickets/pr's that are still based on the 1.x branch but might be helpful for development moving forward. Also some more recent issues and tickets.
Bottom line is that there are quite some issues with generation/deleting images and some requests regarding image manipulations via filters.
Image modification via filters
Images are not deleted
Image transformations error handling and edge cases
towebp
filter always returns new filename, even when conversion was unsuccessful #2875 Unsuccessful conversion dont throw false in some situations.Image transformations feature requests
Image & Attachement classes
Sideloading
The text was updated successfully, but these errors were encountered: