Changing JPEG compression depending on image size

Short. I want ”large’ images compressed by 90%, and ‘medium’ to be by 60%. Has many af you know, sometimes larger images suffer from high compression, but other small images don’t.

This function allows to resample all the jpg images

Read More
function custom_jpg_compression($args) {
    return 90;
}
add_filter('jpeg_quality', 'custom_jpg_compression');

How to filter by image size?

Related posts

Leave a Reply

1 comment

  1. A very special filter

    The jpeg_quality filter is a really special one: It gets used in three different cases and you’ve to use the second argument to determine, if you want to use the filter or not.

    Don’t let it do everything

    The main problem for such a special filter is, that it may fire for later actions, if you ain’t remove it – allow it to run after the first check. So we need to get another filter inside wp_save_image_file() to check if we want to alter the compression or not. To disable it for another save process, we remove it right before altering the compression.

    The kool kid

    The really odd thing is, that WP uses a default compression of 90% (which is 10% reduced quality) for every save process. This means, that every time you upload/crop/edit an image, you reduce its quality… which is a pain for images that aren’t the best, when you upload them (test it with an image containing a lot of red with a high contrast background). But… The really neat thing is, that you can change this behavior. So you want to change the compression, but get increased quality – much better than core allows- at the same time.

    /**
     * Alter the image compression, depending on case
     * 
     * @param  int $compression
     * @param  string $case
     * @return int $compression
     */
    function wpse58600_custom_jpg_compression_cb( $compression, $case )
    {
        global $size_switch;
    
        // Should only fire once - don't leave it in for later cases
        remove_filter( current_filter(), __FUNCTION__ );
    
        // Alter the compression, depending on the case
        switch ( $case )
        {
            case( 'edit_image' ) :
                // We only add the compression, if the switch triggered,
                // which means, that the size is smaller, than set in the main function.
                // 60 is the percentage value, that gets added as compression to the smaller images.
                $compression = $size_switch ? 60 : 100;
                break;
    
            case( 'image_resize' ) :
                // Better leave it on 100% for resize
                $compression = 100;
                break;
    
            case( 'wp_crop_image' ) :
                // Better leave it on 100% for crop
                // We already compressed it on the camera, the desktop/handheld device 
                // and the server previously. That's enough so far.
                $compression = 100;
                break;
        }
    
        return $compression;
    }
    
    /**
     * Alter the compression for JPEG mime type images
     * Checks for a specific min size of the image, before altering it
     * 
     * @param  string $image
     * @param  int $post_id 
     * @return string $image
     */
    function wpse58600_custom_jpg_compression( $image, $post_id )
    {
        global $size_switch;
        $size_switch = false;
    
        // Define the size, that stops adding a compression
        $trigger_size = 641;
    
        // Get the sizes
        $size_x = imagesx( $image );
        $size_y = imagesy( $image );
    
        // Add the filter only in case
        if ( $trigger_size < $size_x )
        {
            $size_switch = true;
        }
        add_filter( 'jpeg_quality', 'wpse58600_custom_jpg_compression_cb', 20, 2 );
    
        return $image;
    }
    add_filter( 'image_save_pre', 'wpse58600_custom_jpg_compression', 20, 2 );
    

    EDIT: After a short discussion with @toscho, he pointed out, that the whole callback could be reduced to the following:

    function wpse58600_custom_jpg_compression_cb( $compression, $case )
    {
        // Should only fire once - don't leave it in for later cases
        remove_filter( current_filter(), __FUNCTION__ );
    
        return ( $GLOBALS['size_switch'] && 'edit_image' === $case ) ? 60 : 100;
    }
    

    As I pulled the code out of a plugin I’m currently working on, I needed the switch to add settings in. I also have to note, that I don’t use the global in my plugin, as it’s an OOP approach. The code you can read ↑ above, is mainly reduced and altered code from the plugin, that has some minor left overs and is meant to be explanatory for later readers and still works. If you want to use it as plugin, you can do some optimization depending on your personal use case.


    Notes:

    From some investigation on the different tasks, there are, I noticed, that multiple $cases get triggered on the following steps:

    • Rotate: edit-image » image-resize (the later 1× for any any size you choose – Thumbnail, etc.)
    • Mirror: edit-image » image-resize (–“–)

    This means, that the filter callback for jpeq_quality will trigger 2× for rotating/mirroring an image and +1× for any additional size you add. So if you got less than 100%, it will reduce the quality twice. I did a lot of research on this topic, but I’m still not completely sure, what exact functions cause this behavior.