Introducing WP Image Processing Queue – On‑the‑Fly Image Processing Done Right

I’ve been bugging core contributors and plugin authors about background processing for a year and half now. To the point where Krogsgard even made fun of me for it at the WordCamp EU after party. “There goes Brad, talking about background processing again.” He was joking, but it’s true. Clearly I think it’s an important subject, but I was surprised to realize that I haven’t talked about it at all on this blog.

Ash wrote about background processing just over a year ago. He presented an awesome library that he coded for WP Offload S3 and released it as a standalone library on GitHub for others to use. I wasn’t the only one who was impressed. WooCommerce and Ninja Forms are using it as well.

The library is great, but I realized there was a problem. If each theme/plugin implements their own background processing queue instead of sharing one queue, we could end up running a bunch of jobs at the same time and impacting server performance. As more and more themes/plugins start to do their own background processing (whether they use this library or do their own thing), the bigger this problem is going to get.

I think the best solution is to get background processing into WordPress core so that all themes/plugins can share a single queue and ensure we don’t impact server performance. And so started my crusade.

At PressNomics, I had a great chat with Mike Schroder. He presented a very good path to core: find a feature that WordPress core needs and that needs background processing. In other words, piggyback! This is exactly how the image optimization stuff made it into core last year: by piggybacking off of responsive images. For background processing, he proposed coming up with an alternative to on-the-fly image processing (OTFIP). Whoa, turns out OTFIP is a problem we regularly deal with for WP Offload S3 as well. This could be a “two birds – one stone” kind of thing. Stars were aligning.

On-the-Fly Image Processing (OTFIP)

We wrote about OTFIP just a few months ago and explained that it tends to cause more problems than it solves. To recap, the main problem OTFIP solves is to cut down on generating (and storing) images that will never be used. If each theme/plugin uses add_image_size() to generate the image sizes they need, the number of image sizes quickly balloons.

Don’t believe me? We have a little competition going on between team members to spot the WordPress install with the most registered image sizes. So far Ash is winning with 49!

the team compares registered image size scores on Slack

To be clear, WordPress generates 49 images of various sizes when an image is uploaded to that WordPress install. That’s easily a minute of waiting for image generation to complete. I’m going to go out on a limb and guess that a solid 29 of those generated images will never be used and yet, they’re taking up hard drive space. This is an extreme example, but we commonly see WordPress installs with 20 or so registered image sizes.

OTFIP FTW

To solve this problem, some developers include OTFIP libraries in their themes/plugins and generate images of the exact size they need when they need it. For example, if you used the popular Aqua Resizer library you might call the following function in your theme/plugin:

<?php $resized_image_url = aq_resize( $image_url, $width, $height, $crop ); ?>

If the resized image doesn’t exist yet, it would be generated and saved alongside the original in the filesystem. This sounds great, but the page load time suffers greatly because it has to wait for the image to be generated before loading the page. And if a page has a bunch of these calls (e.g. one for every blog post on an archive page) it can be a ridiculously long wait and possibly even time out. Plus, image resizing is a CPU and memory intensive operation. So what happens to the server when several of these pages are loaded at the same time? Oops.

Finally, guess what happens when the original image is removed from the Media Library. Yep, the resized image is left behind because WordPress core doesn’t know about it. Plus WordPress core can’t add the resized images to the srcset so no responsive images.

What we need is a solution that doesn’t affect page load time, is mindful of server resources, and works seamlessly with WordPress core as it is today.

Image Processing Queue is Born

At WordCamp US Contributor Day this past December, I decided to try hack together an image processing library that would have all of the benefits of OTFIP but with none of the negatives. And voilà, after a few hours Image Processing Queue was born.

Using it is pretty straightforward. You simply include a file to load Image Processing Queue and its dependencies:

require_once TEMPLATEPATH . '/image-processing-queue/image-processing-queue.php';

Then you can call the ipq_get_theme_image() function in your theme/plugin templates to output an image:

echo ipq_get_theme_image( $post_id, array(
        array( 600, 600, false ),
        array( 1280, 1280, false ),
        array( 1600, 1600, false ),
    ),
    array(
        'class' => 'header-banner'
    )
);

If all the images have already been generated, the output would look something like this:

<img class="alignnone size-full wp-image-22495 header-banner"
width="4096" height="3072" src="https://domain.com/wp-content/uploads/2017/03/image.jpg" 
srcset="https://domain.com/wp-content/uploads/2017/03/image-600x450.jpg 600w,
https://domain.com/wp-content/uploads/2017/03/image-1280x960.jpg 1280w,
https://domain.com/wp-content/uploads/2017/03/image-1600x1200.jpg 1600w,
https://domain.com/wp-content/uploads/2017/03/image-300x225.jpg 300w,
https://domain.com/wp-content/uploads/2017/03/image-1024x768.jpg 1024w"
sizes="(max-width: 720px) 100vw, 720px">

If an image hasn’t been generated yet, it is left out and queued to be generated in the background. So if it’s the first time running the above function call, you might end up with this output:

<img class="alignnone size-full wp-image-22495 header-banner"
width="4096" height="3072" src="https://domain.com/wp-content/uploads/2017/03/image.jpg" 
srcset="https://domain.com/wp-content/uploads/2017/03/image-300x225.jpg 300w,
https://domain.com/wp-content/uploads/2017/03/image-1024x768.jpg 1024w"
sizes="(max-width: 720px) 100vw, 720px">

Regardless of whether the images exist or not, we load the page right away and relegate image generation to the background. No holding up the page load for generating images.

Also, when a new image size is generated, we add metadata to the attachment so that WordPress core understands that the resized image exists. This allows WordPress core to automatically add the generated image sizes to the srcset when generating the image tag. It also allows WordPress core to delete the generated images when the original image is removed from the Media Library. The meta data is in the exact same format as the meta data added for image sizes registered with add_image_size(). Any themes/plugins that use the image size metadata can also use it and therefore use the generated image sizes.

Next Stop, WordPress Core

I would love to see this rolled into WordPress core along with the background processing library. It would make images much easier to manage for WordPress theme/plugin developers and would hopefully result in the abandonment of OTFIP libraries altogether. I’m not sure how something like this would fit into WordPress core’s new release cycles, but I am sure I will continue bugging people about it.

If you’re a theme/plugin author, please consider using Image Processing Queue instead of an OTFIP library next time. You get all the benefits without any of the negatives. And the more this library is used, the easier the case can be made for rolling it into WordPress core.

Of course there is still lots of room for improvement here. Ash is currently working on the background processing library, adding the ability to disable the HTTP worker and spawn multiple queue workers via the CLI. This is great for people with full control of their servers who want more dependable workers. He’s also rebuilding the queue backend, saving jobs to the database by default, but allowing you to swap in any queue backend. For example, you could use Redis to store your jobs.

If you find a bug in either Image Processing Queue or WP Background Processing, please open an issue (or a pull request) on the appropriate GitHub repo.

Have you used OTFIP libraries? Would you give Image Processing Queue a shot instead? What about background processing? What would you like to see happen there? Let us know in the comments.

Update 2017-03-08: I’ve added this library as a plugin on WordPress.org.

About the Author

Brad Touesnard

As founder of Delicious Brains Inc., Brad wears many hats; from coding and design, to marketing and partnerships. Before starting Delicious Brains, Brad was a busy freelance web developer, specializing in front-end development.

  • This sounds great – we try to be a bit more judicious about what sizes we include but we recently took over a client site with exactly this issue.

    https://uploads.disquscdn.com/images/370946639e9538d0c1ac15c3d698f4a8ee055c217de24958e8ed48bcc274a8fa.png

    Compiling these into a srcset is a lovely touch too.

  • Georg Hanisch

    Thanks for sharing this, sounds great!

    How does this work together with WP Offload S3?
    Are generated images automatically transferred to S3 aswell?

    Best,
    Georg

    • Yup, WP Offload S3 will automatically upload the generated images to S3.

      • Gravnetic

        Considering the S3 outage last week do you still stand behind it’s value? I would love to see a S3 implementation that offered a host server fallback similar to how many implement Google served libraries…

        I use the EWWW Image Optimizer Cloud and Imsanity and am very happy with the results. I also always turn off Worpdress image resizing because of the structure that is outlined above in my opinion is not a good solution. Imsanity prevents overly large images and EWWW optimizes uploaded images well.

        Very interesting post though!

      • Georg Hanisch

        Ashley, I am interested how is this done that WP Offload S3 gets to know about those additional sizes? Is this stored somewhere in the Database?

        Edit: Question answered: “Images generated by Image Processing Queue are added to the post meta.”

  • Now we just need to piggyback a solid image compression in there 🙂 Or will WP Offload handle that in the future? 😉

    • You can hook up EWWW right now and it will work with WP Offload S3. We don’t have any plans to offer image optimization services.

      • Could you elaborate a bit on this? Didn’t find anything about Ewww. Is there anything special to be done, or any image generated by your plugin will be compressed (as far as they’re in the /uploads folder)? Thanks.

  • Definitely not a fit for core this year. Would be good to get as a plugin on the directory (instead of just Github) so we can see if it’s useful to more people.

  • Lu

    This looks really good – support for multiple sizes is definitely less hackish than Aqua Resizer which I absolutely love, most of all for its good handling of on the fly cropping.

  • jyaz

    How do you guys handle storage of media library assets? It would be awesome if the Media Library could just be stored on S3 and not mirrored locally, but if the images are not local, it’s not possible to use the WP cropping tool (which is so confusing, I don’t understand how people use it, but many do).

  • wkingio

    How do you set the sizes attribute?

  • Andrew Mathews

    So how do we find out how many images are being generated then.

  • I am just so impressed with you as a developer. You worry about server load, caching, code efficiency, execution time etc, all of which many plugin developers seem to know nothing about, not care about, or don’t know how to do. If you or anyone else knows of a developer as good as you, that wants to join us in developing a BuddyPress / BBPress community for the disability sector, either as an equity partner, employee with profit share etc, then please get in touch: https://mydisabilitymatters.club

  • Ralf Koller

    How about extending the plugin with the concept of image sets to deal with the generation of unnecessary versions of an image. I’ve suggested that over at the Responsive Image plugin about two years ago ( https://github.com/ResponsiveImagesCG/wp-tevko-responsive-images/issues/45 ). If you like the general idea I might update and reshape the proposal and file an issue over at the Image Processing Queue repo on Github.

  • Sallie Goetsch

    Just to be sure I understand the way this is supposed to work: if I install the plugin or include the library, I would then NOT define custom image sizes in functions.php, but rather replace any get_post_thumbnail() or other image calls with ipq_get_theme_image()? Or is this just for things like header image where you aren’t going to use the image size somewhere else? (I’m thinking of cases where a certain post type has a particular size for its featured images that it would make no sense to generate for other post types.)

    • I would then NOT define custom image sizes in functions.php, but rather replace any get_post_thumbnail() or other image calls with ipq_get_theme_image()?

      Yep, you got it.

      • Sallie Goetsch

        Oh, very nice. I hate the way the uploads folder gets cluttered with images at sizes that aren’t used. Someone please tell the creators of Co-Authors Plus to incorporate it in that plugin, which is responsible for creating 5 extra sizes of EVERY image when it should only apply to author avatars.

  • I’ve been struggling with the number of image sizes generated for years, this is a near perfect solution for me. Thanks so much for developing and releasing this! I hope it makes it into core soon.

    I was wondering if you would mind helping with my specific use case. I use the ACF addon, Image Crop Tool and I have specified a fixed image size an image should be cropped to, so in my case, it would be great if the original image size was included into the srcset array. I’ve tried manually specifying the original image width and height inside the ipq_get_theme_image function call, but this doesn’t work. I’m guessing it sees that a crop of this size already exists and stops there.

    How could I make ipq_get_theme_image return the original full image in its srcset list?

    Also, does it matter that the srcset images are not in width order? I thought it was supposed to be smallest width first, then larger. I believe that the browser matches the first valid match but I could be wrong… http://stackoverflow.com/questions/39644823/picture-element-srcset-order-importance

    • The `srcset` stuff is handled by WordPress core. We just pop the metadata into the database that it needs to generate the `srcset`. If I remember right, I believe there was a good reason the WP Core developers didn’t include the original image in the `srcset`. I can’t remember exactly what it was. You might be able to find the answer browsing through old posts at https://make.wordpress.org/core/. There may also be a filter to some kind of mechanism to enable WP to add the originals to the `srcset`.

      • Thanks, I’ll try and figure it out. I run wp_get_attachment_image_srcset, imageID, ‘full’ it returns the full srcset I need, including the new crops generated by wp-image-processing-queue.

        • I had to modify class-image-processing-queue.php in the end, (just in case it helps anyone else, I added ‘full’ to the return wp_get_attachment_image call inside get_image) as I couldn’t find a filter. It works really well now, cheers! The order of the images in srcset doesn’t seem to matter either, when I tested this about a year ago I remember it mattering.

          A nice future addition would be somehow utilising this http://www.responsivebreakpoints.com/ calculation in the ipq_get_theme_image $sizes array. E.g. if the image is above 3000px, have more thumbnails available.

  • Matilde Rosero

    This is awesome. Thank you for sharing!

  • cre8ddesign

    “You get all the benefits without any of the negatives.”

    I’ve seen this type of approach suggested before, but surely a major negative is the fact the first visitor(s) to your site will see a completely wrong image. The majority of times lots of image sizes like this are required are due to them needing to be cropped to exact sizes to fit a certain layout (as opposed to simply scaling, where large numbers of different sizes aren’t really required).

    Sure, it will only affect a small number of visitors to your site – but that’s also true for the case where they have to wait for the image to be cropped before the site displays. I’d rather wait and see the correct results, than not wait and see something broken (or potentially highly unoptimised).

    • A workaround of that problem would be to add a new cropped image size to the list of images sizes generated at upload time. Then there would be a cropped version available even if it isn’t the exact size the theme wants.

      • cre8ddesign

        I can’t see how that would help; themes crop images to specific sizes
        because they need images at exactly that size. Having an image cropped
        to a different size would result in an equally broken look.

        • Why would it be broken?

          • cre8ddesign

            I’m struggling to think of an example where it wouldn’t be 🙂 Say, a grid layout where you wanted images to be cropped to ensure everything lines up. Or a banner where exact dimensions are important due to responsiveness / scrolling effects. Take your Disqus profile picture and imagine you’ve uploaded a tall image that hadn’t been cropped to a square.

            Sure, you could add CSS to provide a fallback mechanism by scaling images to max dimensions, putting them in fixed size boxes with overflow hidden, etc. But if the theme specifically asked for a cropped image, the end result is that you will always be seeing the wrong result.

          • Last time I checked, `wp_get_attachment_image()` respects the aspect ratio of the image size you specify. That is, if you specify a square image, it will only put image sizes that are square into the srcset. So if you generate some larger square images at upload time (in addition to WP’s default square image), it shouldn’t appear broken.

          • cre8ddesign

            Right, sorry, we’re talking about different things. So you’re saying every time you want an image cropped to a new proportion, you have no option but to go back to the original method of generating them for every image – which means this plugin no longer achieves its purpose.

            I guess it cuts down on the number of images if you have a huge set of equal proportions, but the most common use-case for me for on-the-fly image processing is, say, a special widget which displays feature images of posts, where it’s unnecessary to create the special cropped size for every image uploaded to WordPress.