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

jpeg compression / quality #857

Closed
fujo opened this issue Feb 18, 2016 · 16 comments
Closed

jpeg compression / quality #857

fujo opened this issue Feb 18, 2016 · 16 comments

Comments

@fujo
Copy link

fujo commented Feb 18, 2016

Hi everybody

First of all thank you for the great job.

I didn't find how I can set the image quality (jpeg compression). Of course, this topic explains how to set it with a WP filter. However I don't see any changes (file size) in case of TimberImage. Have I missed anything?

Thanx for helping.
Fj

@connorjburton
Copy link

Hi Fujo, have you tested this with new images after you have added the filter to functions.php?

@fujo
Copy link
Author

fujo commented Feb 22, 2016

Hello connorjburton

Thank you for the answer. Yes, it works well.

However I thought that the jpeg was compressed on client request, once the file is called, like the resize function generates new files.

I would like to optimize the image weight but I want to keep the highest image quality on the server.

It could be a great feature if it was possible to define a jpeg compression through twig templates. This function will generates a new file with decreased quality. Like this we could easily manage website/images performance.

I mean something like this: {{post.get_thumbnail.get_src|resize(600, 400)|jpeg_quality(80)}}

Best,
Fj

@connorjburton
Copy link

Thanks for getting back to me. The original link you posted is just the WP filter for compression, it's not related to Timber itself, it will only effect new images, not existing images and won't compress at runtime.

However, with that said, it's possible there is scope for a compression filter in Timber, it's the question of is this something Timber should be handling or should be left to more fully fledged plugins (such as EWWW) to handle. Personally I am not sure how useful specifying a quality per image is, why would you do that over just setting a static quality for all images on the site?

My thoughts summed up:

  1. Ensuring images adhere to the compression quality set by jpeg_quality regardless if they are new images or not is something I'd love to solve, as long as it's not a detriment to performance
  2. I am not sure how useful a quality filter would actually be, open to hear thoughts on this

Be good to get @jarednova thoughts on this

@jarednova
Copy link
Member

@fujo and @connorjburton: I can see the need to set image-level quality settings, but I have to confess that it strikes me as an edge case. The syntax I'd recommend would be something like...

{{post.thumbnail.src | resize(600, 400) | quality(80)}}

The thing this opens up is a bunch of other potential image filters like black-and-white, tint, etc. All of which I can see certain valid use-cases for.

My desired pathway would be for this (and other image filters) to start in TimberSugar or as an image adjustment specific repo. As they demonstrate OH THIS IS REALLY IMPORTANT AND SHOULD BE IN CORE — then great! We can "graduate" them into the core codebase.

@connorjburton
Copy link

I agree, an image manipulation library for Timber would be a good idea, as there are lots of features that could be included.

@fujo, I'd reccomend something like EWWW for now, if you'd like to have a crack at this in Timber, you could do a PR to TimberSugar

@mwld
Copy link

mwld commented Apr 3, 2017

It would be very helpful to be able to control the image compression quality via a Twig filter when using a technique like compressive images (https://www.filamentgroup.com/lab/compressive-images.html) in combination with the element for displaying retina images.

Example: You create a 1x base version with decent quality (like 70%) and 2x, 3x versions that have a way lower quality like 40 or 20% because it saves a lot if bandwidth and the quality difference is barely visible on smartphones with high DPR (see link above).

@colintab
Copy link

Is there already someone who figured this out?

@kevin-chassagne
Copy link

Hi,
You can do something like this:

class TwigExtension {

	public function __construct() {

		add_filter('get_twig', array($this, 'add_to_twig'));

	}

	public function add_to_twig($twig) {

		$filters = $this->getFilters();

		foreach ($filters as $filter)
			$twig->addFilter($filter);

		return $twig;

	}

	public function getFilters() {

		return array(
			new Twig_SimpleFilter('quality', array($this, 'quality')),
		);

	}

	public function quality($src, $quality) {

		$date     = date('Y/m');
		$path     = pathinfo($src);
		$dir      = wp_upload_dir($date);
		$filename = '/' . $path['filename'] . '-q' . $quality . '.' . $path['extension'];

		$img = wp_get_image_editor($src);

		$img->set_quality($quality);

		$newImg = $img->save($dir['path'] . $filename);

		return $dir['url'] . $filename;

	}

}

new TwigExtension();

And

<img src="{{img|resize(500)|quality(50)}}" />
or just
<img src="{{img|quality(50)}}" />

Hope it helps

@columbian-chris
Copy link

columbian-chris commented Feb 6, 2018

I think this would be a fantastic extra parameter of the resize filter. One simple suggestion would be to just add $image->set_quality($quality) to the run method of the lib/Image/Operation/Resize.php file above $result = $image->save($save_filename);.

@gchtr
Copy link
Member

gchtr commented Feb 11, 2018

Cool that the discussion on this starts again. And thanks to @columbian-chris for providing a pull request. I appreciate the work you put into that. And I hate be a party pooper, but I have my concerns about adding another parameter, which I want to explain here.

Are there valid use cases?

First, concerns have been raised in this issue whether it would be useful to set the image quality for individual images, or if all images should have the same quality. To this I can say: Yes, very much! There are a lot of use cases. Just last week we had a use case where we needed to set a certain quality for header images on a site, but not for any other images. The use case that @mwld mentioned plays into this as well. Setting a quality also depends very much on the type of image and how it is used (There’s an interesting article about this here: https://www.netvlies.nl/tips-updates/design-interactie/design-interactie/retina-revolution/).

Other requests for customizing the image

Setting the image quality is actually only one of some requests that want to add an additional parameter to the resize() function. I’m gonna list related requests here so that we can see the full picture that I think actually needs to be addressed.

Changing quality

This feature was already requested in the following pull requests:

Upscaling and Retina

Currently there are still errors and quirks when resizing. Timber doesn’t seem to check whether an image will be upscaled or not. However, when using Retina images and related techniques, we deliberately want to upscale images, just with a smaller image quality. So we need a way to tell resize() how it should handle the height and the width parameters. Wouldn’t this call for another parameter?

Better control of resizing process

  • Feature Request: twig filter to resize & fit - no cropping (1332): We can control a max width and max height for an image, we have the letterbox filter, but there’s not enough control yet to proportionally resize an image to maximum dimensions when both width and height need to be set. This issue calls for another fit parameter.
  • Crop position with coordinates (772): We have different crop modes when resizing an image. But how could we handle a focal point? Yet another parameter that we could eventually add in the future, or extend the crop parameter?

Feature parity with WordPress

  • Extend image manipulation (629): This issue calls for flip and rotation functionality, which are actually missing pieces that would make the Timber image manipulation functionality match the requirements of a WP_Image_Editor class.

Parameters vs. query-style

I’m very much against adding additional parameters to the resize function like it was proposed in the pull request and I make the point for an arguments array instead of parameters.

Especially when handling images, we oftentimes only want to change one parameter. If that’s the last parameter, then we still have to repeat the defaults of all the other parameters. This is not very convenient. We have seen above that we could easily add three more parameters. How would we decide which parameters has more weight?

So instead of writing this:

ImageHelper::resize( 300, 200, true );

I suggest we use an array:

ImageHelper::resize( array( 
    'width' => 300,
    'height' => 200,
    'crop' => true,
) );

And yes, I think we can put all the parameters in the array, because I think there’s not really a required parameter, not even the width. If a width is not provided, a default could be 0. When a height is provided, it would automatically set the width based on the ratio of the original image. If neither width nor height is provided, the returned result could be the same size as the original image. This query-style approach is very prevalent in WordPress, and I think it would make sense to use this more often.

With that, we could also incorporate a quality setting:

ImageHelper::resize( array( 
    'width' => 300,
    'height' => 200,
    'quality' => 75,
) );

In this case, the generated image would have a quality of 75% of the original image. In Twig, this would look like this:

<img src="{{ img|resize({ width: 300, height: 200, quality: 75 }) }}">

For brevity, we could also allow shorthands:

<img src="{{ img|resize({ w: 300, h: 200, q: 75 }) }}">

I realized that we actually use a similar style in Timmy, where we set how an image should be resized in a configuration, which is kind of the same as a query-style parameter, just globally set in a central place. For us it works well. But it is an opionated approach.

Other operations using quality

I guess the image quality is not something that is restricted to the resize function. It could also be used in all other filters that Timber provides: Letterbox, ToJpg, Retina, and probably future filters.

Where should we go from here?

I think we need to adapt our approach for handling images. And I realize this is going to be quite some work, if we consider the points above. There are actually more points we need to consider, where I’m not going to list the related issues and pull request, though:

  • Make images work with CDN
  • Converting to other image formats
  • Convert to other color profiles
  • Regenerating images
  • Responsive image markup helper

I do think we have a really good base. I don’t mean we should start from scratch. But the upcoming version 2 of Timber would actually be good moment to start introducing breaking changes like changing the parameters for resize(). For this, we have to consider how we want to handle future changes, so that the don’t have to introduce more breaking changes, but can build on a solid base that is extendable.

Maybe we need to think less about Twig filters? Twig filters are not the only way how images are handled when using Timber. I consider a Twig filter the solution for simple use cases. But I don’t like endless chaining either. Chaining can also lead to problems when the proper order is not considered.

That’s one reason why I’ve built Timmy, which I don’t want to promote here, but it gave me many insights into image handling.
I don’t know what the best approach to handle so many different features would be. Maybe we need only one image manipulation function where different operations like letterbox and image format conversion like toJPG can hook into? I’m a little guilty of using this configuration style a lot:

<img src="{{ img|manipulate({
    width: 300,
    height: 200,
    quality: 75,
    allow_upscale: false,
    fit: true,
    crop: 'something',
    letterbox: '#fff',
    format: 'webp',
    profile: 'rgb'
}) }}">

What other approaches do we have?

And we need to think more about using global WordPress hooks to use settings. As you already know, image quality can already be filtered through wp_editor_set_quality. We should make sure we have the proper filters to control other manipulation values as well.

Oh man, this was a long answer 🙃. What’s everybody else thinking?

@columbian-chris
Copy link

@gchtr What an extensive follow-up answer; thank you! So much great insight on this issue.

I personally like the idea of just adding on a single optional extra parameter to the resize filter for the current version to maintain backwards compatibility and maybe then focus on a long term plan for 2.x.

@jarednova
Copy link
Member

@gchtr thanks so much for your thoughtful (impassioned?) response on this. I share the concerns about runaway variables. Rather than wait, I think we can all agree that we should handle for the WP preferences (the wp_set_image_quality filter) and get that functionality in ASAP (I've opened pull request #1666 to address this).

My mind can generally handle about two arguments, after that there's no hope of remembering the order. Thus, this works for me...

<img src="{{ image.thumbnail | resize(1600, 1200) }}" />

but forget about

<img src="{{ image.thumbnail | resize(1600, 1200, 'top', true, 50) }}" />

This is where the notion of args becomes useful...

<img src="{{ image.thumbnail | resize(1600, 1200, {crop: "top", quality: 50}) }}" />

And now that I see that actually written out, I can see why:

<img src="{{ image.thumbnail | resize({w: 1600, h: 1200, crop: "top", quality: 50}) }}" />

... is actually cleaner (despite having to write out the parameters for "w" and "h").

I think where I part with you is "manipulate" vs "resize." While "manipulate" is certainly more broad and versatile, "resize" describes what the template-writer is probably looking to accomplish 95% of the time (plus it's less characters and harder to misspell, which are real considerations).

I have no objection to making one an alias of the other if you think that helps keep things broad

@gchtr
Copy link
Member

gchtr commented Feb 19, 2018

I personally like the idea of just adding on a single optional extra parameter to the resize filter for the current version to maintain backwards compatibility and maybe then focus on a long term plan for 2.x.

@columbian-chris I’d be fine with that, too. But I guess the plan is to make 2.x the next version to come out.

@jarednova Yeah, I’m torn as well. I currently can’t come up with a better solution to handle all the different parameters.

Oh, and I didn’t think too much about the term "manipulate", to be honest. English is not my mother tongue, so it’s even harder for me to name things. I guess I used it more as a hint that we could also think about the word "resize". I thought if – in the future – we’ll maybe have a use case like the following, it would be more like a conversion than a resize, and manipulate could be a more general term:

<img src="{{ image.thumbnail | resize({quality: 50, profile: 'rgb'}) }}" />

But on the other hand, you’re right that "resize" is way easier to write and remember and probably covers 95% of the use cases, so I’m fine with leaving that as it is (without an alias).

@blimpmason
Copy link

blimpmason commented Jan 18, 2019

Hi all, very excited about the potential to control image quality and other parameters at the image level.

Also just wanted to clarify one thing:

I've set the global Wordpress jpeg quality using the 'jpeg_quality' filter, and newly added images generate thumbnails according to that quality setting, but resized Timber images are not. The Timber generated images seem to be using the Wordpress default rather than the quality set by the filter as I am not seeing any difference in quality or filesize.

Is that the current state of Timber or is there a workaround I'm overlooking? Is that what @jarednova meant in his comment above:

Rather than wait, I think we can all agree that we should handle for the WP preferences (the wp_set_image_quality filter) and get that functionality in ASAP (I've opened pull request #1666 to address this).

@gchtr
Copy link
Member

gchtr commented Jan 19, 2019

Hey @blimpmason

The wp_editor_set_quality is implemented in Timber, but not the jpeg_quality filter. According to the documentation, the jpeg_quality filter…

Filters the JPEG compression quality for backward-compatibility.

So jpeg_quality seems to be an outdated filter. It probably works if you use the wp_editor_set_quality filter. This would also explain, why your thumbnails are generated with the quality setting you choose with the jpeg_quality filter and not when you resize them with Timber.

@blimpmason
Copy link

@gchtr, thanks so much for clarifying that — using the correct wp_editor_set_quality filter works perfectly with Timber resize!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants