14

Is it possible to set the image quality based on the image size? I'd like to have better image quality for larger images (80) - and worse for small thumbnails (30).

I was expecting a parameter at add_size to control that - but there is none.

If it matters: I'm using ImageMagick.

14
+50

The only time setting the quality really matters is right before the image is saved or streamed (for the editor). Both of these have the "image_editor_save_pre" filter there, passing it the instance of the image editor. So you can use that to modify the image in any way you like, including setting the quality.

So, something like this should do the job simply and easily:

add_filter('image_editor_save_pre','example_adjust_quality');
function example_adjust_quality($image) {
    $size = $image->get_size();
    // Values are $size['width'] and $size['height']. Based on those, do what you like. Example:
    if ( $size['width'] <= 100 ) {
        $image->set_quality(30);
    }
    if ( $size['width'] > 100 && $size['width'] <= 300 ) {
        $image->set_quality(70);
    }
    if ( $size['width'] > 300 ) {
        $image->set_quality(80);
    }
    return $image;
}
  • Reason why I didn´t use something as straight as this one (+1) is, that I remember vaguely that when editing some image (rotating, cropping, etc.), every action was called twice, reducing the quality two times. Still the "is an instance of WP_Image_Editor" part is a much more of a solution than what I wrote. – kaiser Mar 26 '14 at 17:37
  • 1
    The quality is an exact value, not a percentage. You can set and reset it all you like, until it saves. Setting it to 10 a hundred times leaves it at 10. – Otto Mar 27 '14 at 2:57
  • I assumed it would save in between. Thanks for the heads up. – kaiser Mar 27 '14 at 7:57
  • It seems to me that image_editor_save_pre doesn't get called. When I try to output stuff using error_log (which definitely works) I don't get any output. :/ – Nils Riedemann Mar 27 '14 at 10:56
  • 1
    Regenerating might work too, if it resaves the image. No code is going to change existing files on the system without you actually taking the action to reload and resave them. – Otto Mar 28 '14 at 22:52
5

Note upfront: Below answer isn't finished and is not tested, but I ain´t got enough time left, so I'll leave this here as draft. What probably needs a second pair of eyes is the quality method and the interpretation of version_compare().

First we need an entry point. After reading through the make post again, I thought best would be to jump in before the Image Editor saves the newly created image. So here's a micro controller that intercepts during a callback hooked to image_editor_save_pre and loads a class that then walks through your settings defined inside a callback to wpse_jpeg_quality. It simply returns different compression ratios for the jpeg_quality filter that runs inside the Image Editor.

<?php

namespace WPSE;

/**
 * Plugin Name: (#138751) JPEG Quality Router
 * Author:      Franz Josef Kaiser
 * Author URI:  http://unserkaiser.com
 * License:     CC-BY-SA 2.5
 */

add_filter( 'image_editor_save_pre', 'WPSE\JPEGQualityController', 20, 2 );
/**
 * @param string $image
 * @param int $post_id
 * @return string
 */
function JPEGQualityController( $image, $post_id )
{
    $config = apply_filters( 'wpse_jpeg_quality', array(
        # Valid: <, lt, <=, le, >, gt, >=, ge, ==, =, eq
        'limit'      => 'gt',
        # Valid: h, w
        'reference'  => 'w',
        'breakpoint' => 50,

        'low'        => 80,
        'high'       => 100,
    ) );
    include_once plugin_dir_path( __FILE__ ).'worker.php';
    new \WPSE\JPEGQualityWorker( $image, $config );

    return $image;
}

The actual worker is the JPEGQualityWorker class. It resides in the same directory as above main plugin file and is named worker.php (or you change the controller above).

It retrieves the image and your settings and then adds callbacks to the jpeg_quality filter. What is does is

  • retrieving your image reference (width or height)
  • questioning your breakpoint that decides where to switch between low and high quality/compression ratio
  • retrieving the original image size
  • deciding what quality to return

The breakpoint and the limit is what decide between high and low and as mentioned above this might need some more love.

<?php

namespace WPSE;

/**
 * Class JPEGQualityWorker
 * @package WPSE
 */
class JPEGQualityWorker
{
    protected $config, $image;
    /**
     * @param string $image
     * @param array $config
     */
    public function __construct( Array $config, $image )
    {
        $this->config = $config;
        $this->image  = $image;

        add_filter( 'jpeg_quality', array( $this, 'setQuality' ), 20, 2 );
    }

    /**
     * Return the JPEG compression ratio.
     *
     * Avoids running in multiple context, as WP runs the function multiple
     * times per resize/upload/edit task, which leads to over compressed images.
     *
     * @param int $compression
     * @param string $context Context: edit_image/image_resize/wp_crop_image
     * @return int
     */
    public function setQuality( $compression, $context )
    {
        if ( in_array( $context, array(
            'edit_image',
            'wp_crop_image',
        ) ) )
            return 100;

        $c = $this->getCompression( $this->config, $this->image );

        return ! is_wp_error( $c )
            ? $c
            : 100;
    }

    /**
     * @param array $config
     * @param string $image
     * @return int|string|\WP_Error
     */
    public function getCompression( Array $config, $image )
    {
        $reference = $this->getReference( $config );
        if ( is_wp_error( $reference ) )
            return $reference;
        $size = $this->getOriginalSize( $image, $reference );
        if ( is_wp_error( $size ) )
            return $size;

        return $this->getQuality( $config, $size );
    }

    /**
     * Returns the quality set for the current image size.
     * If
     * @param array $config
     * @param int $size
     */
    protected function getQuality( Array $config, $size )
    {
        $result = version_compare( $config['breakpoint'], $size );
        return (
            0 === $result
            AND in_array( $config['limit'], array( '>', 'gt', '>=', 'ge', '==', '=', 'eq' ) )
            ||
            1 === $result
            AND in_array( $config['limit'], array( '<', 'lt', '<=', 'le', ) )
        )
            ? $config['high']
            : $config['low'];
    }

    /**
     * Returns the reference size (width or height).
     *
     * @param array $config
     * @return string|\WP_Error
     */
    protected function getReference( Array $config )
    {
        $r = $config['reference'];
        return ! in_array( $r, array( 'w', 'h', ) )
            ? new \WP_Error(
                'wrong-arg',
                sprintf( 'Wrong argument for "reference" in %s', __METHOD__ )
            )
            : $r;
    }

    /**
     * Returns the size of the original image (width or height)
     * depending on the reference.
     *
     * @param string $image
     * @param string $reference
     * @return int|\WP_Error
     */
    protected function getOriginalSize( $image, $reference )
    {
        $size = 'h' === $reference
            ? imagesy( $image )
            : imagesx( $image );

        # @TODO Maybe check is_resource() to see if we got an image
        # @TODO Maybe check get_resource_type() for a valid image
        # @link http://www.php.net/manual/en/resource.php

        return ! $size
            ? new \WP_Error(
                'image-failure',
                sprintf( 'Resource failed in %s', get_class( $this ) )
            )
            : $size;
    }
}
  • Still working on it? As far as I'm concerned I'd love to see that as a plugin. – Nils Riedemann Mar 26 '14 at 9:37
  • Sorry, but no I´m not. I´d like to see that as plugin as well, but as I currently don´t need it and don´t have the time, it won´t happen so far. Maybe give it a shot, see how far you get and file an edit or a separate answer? :) – kaiser Mar 26 '14 at 12:46
  • kaiser: You overcomplicated it, I think. The $image sent to image_editor_save_pre is an instance of the WP_Image_Editor class. It has functions to get the size and set the quality and everything else already in it. All you have to do is call them. – Otto Mar 26 '14 at 17:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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