7

I'm trying to come up with a solution where the generated image formats cover both responsive and high-resolution aspects when uploading a new item in the WP admin. I've put together a set of formats that should meet these requirements,

2560 x 1600 retina desktop ('largex2', custom)
1280 x 800 desktop (large)
1920 x 1200 retina tablet ('mediumx2', custom)
960 x 600 tablet + retina mobile (medium)
480 x 300 mobile (thumbnail)

My problem lies in the image quality for the larger sizes. Exporting the images from Photoshop at a quality setting of high or very high will produce fair results for the default sizes, but what about the custom ones? Those I would rather export at a medium or low setting to keep a reasonable file size.

My thought is - would it be possible to take one really big image at a high quality, upload it, then set so that the bigger sizes are generated at a much lower quality?

Any help is appreciated! Please do not direct me to any plugins, I want to build my own solution for this.

1

2 Answers 2

8
+150

1) A workaround by extending the WP_Image_Editor_GD class

The problem is how to access the image sizes before we change the quality of intermediate jpeg images.

Note that the image_resize() function is deprecated.

We can use the jpeg_quality filter from the public get_quality method of the abstract WP_Image_Editor class:

$quality = apply_filters( 'jpeg_quality', $quality, 'image_resize' );

or the wp_editor_set_quality filter.

Here's one idea how to set the image quality based on the image size (width/height):

/**
 * Set image (jpeg) quality based on the image size (width/height)
 * @see http://wordpress.stackexchange.com/a/165241/26350
 */
add_filter( 'wpse_make_image_arguments', function( $arguments, $filename, $size, $function ) 
{   
    // Only target jpeg cases, i.e. with the quality set in $arguments[2]       
    if( ! isset( $size['height'] ) || ! isset( $size['width'] ) || ! isset( $arguments[2] ) )
        return $arguments;

    // Modify this part to your needs:
    if( $size['height'] <= 150  && $size['width'] <= 150 )
        $arguments[2] = 2; // very low quality for easy testing

    return $arguments;
}, 10, 4 );

where we've extended the WP_Image_Editor_GD class:

/**
 * Extend the WP_Image_Editor_GD to add the custom wpse_make_image_arguments filter.
 * @see http://wordpress.stackexchange.com/a/165241/26350
 */
add_filter( 'wp_image_editors', function( $editors ) 
{
    // Note that the WP_Image_Editor_GD and WP_Image_Editor_Imagick classes
    // are included within this filter. So let's do the same for our extended class.

    // Our extended class that overrides the WP_Image_Editor_GD::make_image() method

    if( ! class_exists( 'WPSE_Image_Editor_GD' ) )
    {   
        class WPSE_Image_Editor_GD extends WP_Image_Editor_GD
        {
            protected function make_image( $filename, $function, $arguments ) 
            {
                // Add a custom filter      
                $arguments = apply_filters( 'wpse_make_image_arguments', $arguments, $filename, $this->size, $function );

                // Parent method
                return parent::make_image( $filename, $function, $arguments );
            }
        }
    }

    // Prepend the extended class to the array of image editors:    
    array_unshift( $editors, 'WPSE_Image_Editor_GD' );

    return $editors;
} );

where we introduced the custom wpse_make_image_arguments filter.

This way we can modify the quality settings, before the intermediate files are saved.

Here's an example:

Example

PS: I didn't check out the case when the Imagick library is used instead, but I guess we could do something similar by extending the WP_Image_Editor_Imagick class.

2) Update - Set jpeg quality per image size name

Here's another version where we set the jpeg quality per image size name:

/**
 * Extend the WP_Image_Editor_GD to set quality per image size name.
 * 
 * @see http://wordpress.stackexchange.com/a/165241/26350
 */
add_filter( 'wp_image_editors', function( $editors ) 
{
    // Note that the WP_Image_Editor_GD and WP_Image_Editor_Imagick classes
    // are included within this filter. So let's do the same for our extended class.

    // Our extended class that overrides the WP_Image_Editor_GD::_resize() method
    if( ! class_exists( 'WPSE2_Image_Editor_GD' ) )
    {   
        class WPSE2_Image_Editor_GD extends WP_Image_Editor_GD
        {
            protected function _resize( $max_w, $max_h, $crop = false )
            {
                $qualities = apply_filters( 'wpse_jpeg_qualities', [] );
                $default_quality = (int) apply_filters( 'wpse_default_jpeg_quality', 82 );                              
                $sizes = wp_get_additional_image_sizes();
                $this->set_quality( $default_quality );         
                foreach( (array) $qualities as $name => $quality )
                {
                    if( 
                        isset( $sizes[$name] ) 
                        && (int)  $sizes[$name]['width']  === (int)  $max_w
                        && (int)  $sizes[$name]['height'] === (int)  $max_h
                        && (bool) $sizes[$name]['crop']   === (bool) $crop  
                    )   
                        $this->set_quality( $quality );                 
                }

                // Parent method
                return parent::_resize( $max_w, $max_h, $crop );
            }
        }
    }

    // Prepend the extended class to the array of image editors:    
    array_unshift( $editors, 'WPSE2_Image_Editor_GD' );

    return $editors;
} );

I noticed that the crop arguments can be 0, false or empty, so we do some typecasting to be sure.

Here we've introduced the following new filters:

add_filter( 'wpse_jpeg_qualities', function( $qualities )
{ 
    return [ 'hello-image' => 2, 'medium' => 2, 'large' => 2 ];
} );

and

add_filter( 'wpse_default_jpeg_quality', function( $quality )
{ 
    return 82;
} );

that can hopefully be adjusted to your needs!

9
  • Wow, what a thorough and nicely explained idea. Thanks birgire! While I did go for the solution kraftner suggested I will definitively try this out. Commented Oct 13, 2014 at 15:00
  • @StaffanEstberg It looks like this answer is a much better solution, but I haven't tested it ;-)
    – birgire
    Commented Oct 13, 2014 at 16:11
  • @StaffanEstberg It looks like the answer I linked to only applies to the image editor, but not when you upload the image.
    – birgire
    Commented Oct 13, 2014 at 16:25
  • Ok. Thanks for the heads up though. Commented Oct 13, 2014 at 19:04
  • 2
    I almost managed to solve this with only filters (intermediate_image_sizes_advanced, image_resize_dimensions and jpeg_quality filter) within a custom class, but was stopped by single line in the get_quality() method of the WP_Image_Editor class. It's the check: if ( ! $this->quality ), that's made before the jpeg_quality filter is activated. That means you can only use that filter once, for all intermediate jpeg image sizes. So if we want to modify the quality before the files are saved, then I don't see another way then to extend the class as I did in my answer ;-)
    – birgire
    Commented Oct 21, 2014 at 8:56
1

Cant comment, have to post together with another version.

The problem(s):

Im not shure if the Update 2) Set jpeg quality per image size name in the accepted answer is complete. wp_get_additional_image_sizes() returns the global $_wp_additional_image_sizes and does not contains any information about default sizes as medium, large or thumbnail.

This might not work in the filter: 'large' => 2

As of https://codex.wordpress.org/Function_Reference/get_intermediate_image_sizes explains with custom code to retrive ALL sizes with a custom function.

Am I right?

Second, The idea of filter diffrent "sizes" by name is tricky. _resize( $max_w, $max_h, $crop = false ) only understand and parses matched width, height and crop value, and every match suppose to match a name. But in many cases the name will be "another" name.

A "medium" image setting that has a equal "shop_product" setting will only "visit" this function as one of them. Then we wont know if it is "medium" size or "shop_product" size thats being filtered. Because, the objectives here is just a technical crop of an image.

The idea with Update 2) is a more logic handeling, but Im afraid that the techncal architecure is not there. To analyze all registered image sizes before constructing the filter, becomes more development to make shure the intention returns the correct quality image program on current Theme.

All size names with equal settings will share the same new quality as they share the same image on the server.

So, if you still stick to the update 2) I think you need to call another function to populate the $sizes variable, and analyse by var_dump() the $_wp_additional_image_sizes for equal settings.

The approach in 1) A workaround by... is more logic/ semantic. We solved this by the same extending method, but using a dynamic filter instead:

add_filter('wp_image_editors', function($editors){

    if(!class_exists('ENTEX_Image_Editor_GD')){   
        class ENTEX_Image_Editor_GD extends WP_Image_Editor_GD {
            private $wp_quality = null;
            protected function _resize($max_w, $max_h, $crop = false){
                $condition = ($crop ? 'true' : 'false');
                if($this->wp_quality === null) $this->wp_quality = $this->get_quality();
                $quality = apply_filters('wp_editor_set_quality_'. $max_w .'x'. $max_h .'_'. $condition, $this->wp_quality);                              
                $this->set_quality((int) $quality);
                return parent::_resize( $max_w, $max_h, $crop );
            }
        }
    }    
    array_unshift($editors, 'ENTEX_Image_Editor_GD');
    return $editors;
});

To filter each individual image size we then use:

add_filter('wp_editor_set_quality_160x160_true', function($quality){
    return 96;
});

Whats also diffrent from this extended version is that we are using/ populates the add_filter('jpeg_quality' ... version as default value.

Here are some other examples of custom filters:

function high_quality_on_medium_images(){
    $in = 'medium';
    $max_w = get_option($in.'_size_w');
    $max_h = get_option($in.'_size_h');
    $crop = get_option($in.'_crop');
    $condition = ($crop ? 'true' : 'false');
    add_filter('wp_editor_set_quality_'. $max_w .'x'. $max_h .'_'. $condition, 'my_theme_high_quality_images', 10, 1);
}
add_action('after_setup_theme', 'high_quality_on_medium_images', 99);

function my_theme_high_quality_images($quality){
    return (int) 82;
}

Developers attention

  • Make shure filters are applied after the Theme images setup.
  • Dont wrap inside is_admin() then ajax call, front end, remoted
    posted images wont bite.

As mentioned, the crop value could be either 0 or false, you must convert (int) or (bool) into a string with $condition = ($crop ? 'true' : 'false');

Theme developers attention

The WP default compression is quite progressive. Values between 80 and 99 could return a huge difference or no visible result at all. A none comression value as 100, likely returns a bigger filesize on "smaller sized" 800 px images, then the original big 3000 px sized version.

After many years we found a magic number of 96, the filesize get smaller but you cant see the diffrence between a 100 value compression.

High quality photoshop prepared shop images, bigger then 800px is fine with Wordpress default 82. But iPhone "Large" email posted image really need a 96 value on SMALL sizes like thumbnails. They become "soften blur" on Wordpres compressions.

You will over-do with this topic solutions if you not planning the scenarios for your project, users or Theme.

A final reflexion

Im really dissapointed at Wordpress that this important issue is neglected in the priority of development, as images are more then ever important to be unique effective onload.

Wy cant we just get a filter in right place that can be used for individual compression? They bother to "change the name" to wp_editor_set_quality, and add_filter('jpeg_quality' ... is now deprecated. Why not think this trought all the way and just remove the if ( ! $this->quality ) only check once method as mentioned by @birgire.

1

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.