WordPress - Blur Image on Upload
Asked Answered
S

6

10

So I'm following the example given here (which I modified to only blur, no watermark), to make a blurred image in WordPress on upload. The problem is, that if the uploaded file is the exact same size, or smaller, than the set size, then WordPress will not generate an image, and hence no blurred one will be made.

I tried using a isst($meta['sizes']['background-image-blurred']['file']) to determine if one was made, and if not then copy() the source file, but then no WordPress "metadata" would be generated for the image (for non-WordPress people, the metadata is different than what you think), so it would give height/width undefined problems when displaying using wp_get_attachment_image.

So I'm convinced using wp_get_attachment_image hook as shown below is probably the wrong way to do this. It probably needs to happen earlier in the image upload process.

Any ideas on how to best get this working?

/**
 * Several functions relatting to blurring images on uploaded.
 * @see https://codeable.io/community/how-to-watermark-wordpress-images-with-imagemagick/
 */ 
    add_image_size( 'background-image-blurred', 1920, 1080, true );

    function generate_blurred_image( $meta ) {

      $time = substr( $meta['file'], 0, 7); // Extract the date in form "2015/04"
      $upload_dir = wp_upload_dir( $time ); // Get the "proper" upload dir

      $filename = $meta['sizes']['background-image-blurred']['file'];
      $meta['sizes']['background-image-blurred']['file'] = blur_image( $filename, $upload_dir );

      return $meta;

    }
    add_filter( 'wp_generate_attachment_metadata', 'generate_blurred_image' );    

    function blur_image( $filename, $upload_dir ) {

      $original_image_path = trailingslashit( $upload_dir['path'] ) . $filename;

      $image_resource = new Imagick( $original_image_path );
      $image_resource->gaussianBlurImage( 10, 100 ); // See: http://phpimagick.com/Imagick/gaussianBlurImage

      return save_blurred_image( $image_resource, $original_image_path );

    }    

    function save_blurred_image( $image_resource, $original_image_path ) {

      $image_data = pathinfo( $original_image_path );

      $new_filename = $image_data['filename'] . '-blurred.' . $image_data['extension'];

      // Build path to new blurred image
      $blurred_image_path = str_replace($image_data['basename'], $new_filename, $original_image_path);

      if ( ! $image_resource->writeImage( $blurred_image_path ) ) {
        return $image_data['basename'];          
      }

      // Delete the placeholder image WordPress made now that it's been blurred
      unlink( $original_image_path );

      return $new_filename;

    }    
Scantling answered 20/12, 2015 at 1:49 Comment(3)
FYI it has to be server side blurring. I tired CSS/SVG/JS blurring, and it's just not efficient enough given all the other animations I have going on.Scantling
"The problem is, that if the uploaded file is the exact same size, or smaller, than the set size" I'm not sure to understand quite right this, you're trying to replace an existing image?Palace
No, I'm trying to generate a new blurred version of the uploaded file. What that comment related to is wp_generate_attachment_metadata is only called if a new version is made (and if the size is too small or the same, nothing new is made).Scantling
P
4

Unfortunately wp hasn't got a filter to force a size so what you can do is hook in after and resize your image if not created and pop it into the metadata.

I haven't got imagick so you will have to try these functions yourself, but you have the correct process above, you just need to update the filename and type in the array below. PS don't output anything within the filter!

function custom_img_size(){
    add_image_size( 'background-image-blurred', 1920, 1080, true );
}

add_action( 'after_setup_theme', 'custom_img_size' );


add_filter('wp_generate_attachment_metadata', 'force_add_size', 100);
function force_add_size( $metadata ) {

   if(!isset($metadata['sizes']['background-image-blurred'])){
        //not set so initiate our custom size...
        //I dont have imagick installed so just use your functions here to duplicate
        //note original file = $filename update the $newfilename below...
        //sample resize code ...
        $upload_dir = wp_upload_dir();
        $filename= $upload_dir['basedir'].'/'.$metadata['file'];
        $extension = strtolower(strrchr($metadata['file'], '.'));
        $newfilename= str_replace($extension, '-1200x1080', $filename).$extension;

        copy($filename, $newfilename );
        //end sample resize code.....



        $filetype= 'image/jpeg';
        $metadata['sizes']['background-image-blurred']= array(
            "file"=> $newfilename,
            "width"=> 1920, 
            "height"=> 1080,
            "mime-type"=> $filetype 
        );

   }


   return $metadata;

}

Updates

  1. This is designed to only catch where your existing filter has failed to create your blurred custom size otherwise it does nothing. You should still include your original filters. You may have an issue in the original code: You are deleting the original file in your filters and this will cause issues as there is a postmeta field called '_wp_attached_file' that will need updating. I have included a note below on this.

  2. The filter catches the metadata before saving so any changes are also going to be saved once you return the $metadata. If you look at the source code: https://core.trac.wordpress.org/browser/tags/3.8.1/src/wp-admin/includes/image.php#L72 here you can see exactly how it works. I've also confirmed using wp4.3

  3. I have attempted to insert the imagick functions you need below. I havent tested as i dont actually have it installed anywhere. (imagemagick is actually a wonderful opensource program but very server intensive). Try this function in place of the one above:

    add_filter('wp_generate_attachment_metadata', 'force_add_size', 100, 2);
    
    function force_add_size( $metadata, $id ){
    
        $upload_dir = wp_upload_dir();
        $filename= $upload_dir['basedir'].'/'.$metadata['file'];
        $extension = strtolower(strrchr($metadata['file'], '.'));
        $newfilename= str_replace($extension, '-blurred', $filename).$extension;
    
        $image_resource = new Imagick( $filename);
        $image_resource->resizeImage(1920,1080,Imagick::FILTER_LANCZOS,.3);
        $image_resource->writeImage( $newfilename );
        //http://www.dylanbeattie.net/magick/filters/result.html
    
        unlink( $filename );//delete original image altogether ---> you might want to update the post meta on this '_wp_attached_file' , you can actually get the attachment id from the filter, i have added it above. It would be best to have a actual image url in there! something like $sfile= str_replace($upload_dir['basedir'],'', $newfilename); update_post_meta($id, '_wp_attached_file', $sfile );
    
    
    
        switch($extension){
            case '.jpg':
            case '.jpeg':
                $type = 'image/jpeg';
                break;
            case '.gif':
                $type = 'image/gif';
                break;
            case '.png':
                $type = 'image/png';
                break;
            default:
                $type = 'image/jpeg'; // you might want a conditional to check its actually a image...imagick will do this for you as well....it shouldnt get this far if not a image.
                break;
        } 
    
        $metadata['sizes']['background-image-blurred']= array(
            "file"=> $newfilename,
            "width"=> 1920,//your custom image size, has to be this! you could get the global var and check for sizes but its this size in particular we want? 
            "height"=> 1080,
            "mime-type"=> $type 
        );
    
        return $metadata;
    }
    

update to prevent the image stretching out smaller images replace the imagick code with this.

$upload_dir = wp_upload_dir();
$filename= $upload_dir['basedir'].'/'.$metadata['file'];
$extension = strtolower(strrchr($metadata['file'], '.'));
$newfilename= str_replace($extension, '-blurred', $filename).$extension;

$image_resource = new Imagick( $filename);


if($image_resource->getImageWidth() <= 1920 || $image_resource->getImageHeight() > <= 1020) {
    return $metadata;
}

$image_resource->resizeImage(1920,1080,Imagick::FILTER_LANCZOS,.3);
$image_resource->writeImage( $newfilename );
//http://www.dylanbeattie.net/magick/filters/result.html
Parsonage answered 29/12, 2015 at 0:37 Comment(15)
I think this is probably the way to go. Not a complete answer, but the best I have in the 7 day bounty period.Scantling
what do you feel is missing and ill have a look?Parsonage
Well I'm not 100% on where the Imagick code needs to go, and the image is not always going to be a image/jpeg right? And you're hard coding the height/width to be 1920x1080, which it might not always be (and adding a -1200x1080 to the filename too. Also, I think wp_generate_attachment_metadata filter is run after the metadata is created, so the new blurred image won't have its meta data added to the database correctly (I think, I need to test that).Scantling
David, this seems to work, except it deleted the original image too. I need both the non-blurred, and the blurred image. Any ideas?Scantling
I commented out the unlink and it worked, but it seems that the aspect ratio changes between the blurred and not blurred images.Scantling
I think the problem is the hard coding of 1920x1080. That dimension is the "max" is should be, but it should be able to be smaller, just crop if its larger, but always generate a blurred version, and keep both unblurred and blurred.Scantling
Also, when using wp_get_attachment_image_src() to get blurred image it will return the URL like this: ../cliff-horizon-lake-197-1920x1080-blurred.jpg, which will 404, but this will work ../cliff-horizon-lake-197-blurred.jpgScantling
ill have a look later, but what i think you are saying is if the image is larger than the dimensions, crop and if lower you dont need the function to run? ill update the function to add the dimensions to the filename which will solve the 404. you are correct the unlink function deletes the original.Parsonage
It should always generate a blurred image. But sometimes the image will not be 1920x1080. If the source image is 612x433, it should make a 612x433 blurred image, and a leave the source as is.Scantling
but i thought your original function worked for this? The issue was getting it to work with a custom image size that may be larger than the original?Parsonage
I made a gist of the complete function here: gist.github.com/drewbaker/6cbfdaff1489b7990d04 If the image you upload is any dimension other than 1920x1080, it will stretch it, breaking the aspect ratio. It also times out on my server due to how long it takes to generate the images.Scantling
its a bit old to be used. Imagick is fairly resource heavy you need a good server to use it quickly, ideally its own, or you can split this into a couple of operations e.g. use a cron job to find images to blur. I get the stretch thing, its easy enough to solve, if less than either of the two dimensions, don't run. Its a huge image and not very practical anyway!Parsonage
David you should update your profile to have some contact information on it.Scantling
Hi Drew, I don't really like to display information publically! Is this imagick still giving you issues?Parsonage
Yeah, thinking of trying to get this wrapped into a pluginScantling
K
1

When I was first reading your question I was unsure if you were having problems setting the image or generating the image or both. I thought the issue with setting the image might be that you needed to update the post image with wp_update_attachment_metadata after using wp_generate_attachment_metadata to have the post use the new image instead.

When I reread your question I realized it had to be an issue with the add_image_size() because it happens with an image the same size or smaller than the one uploaded. I had once run into this issue as well with (re)generating an alternate size image for a new theme. In my case it was that either the width or the height parameter was not being met on add_image_size().

I wanted to verify this in the Wordpress Code Reference and found a comment by a contributor near the bottom that is exactly the same issue you face, albeit without a solution posted.

If you upload an image whose dimensions match the add_image_size() when crop is set to true, in the $meta object accessed by the wp_generate_attachment_metadata filter, that matching image size will not be available. Also, image sizes that have larger dimensions than an uploaded photo will not be available either.

(Thus if you are using a technique to create something like a monochrome derivative image, you won’t be able to get it to work if the uploaded image is exactly the same size as the image size you’re using for your black and white version).

I think in your case there is a workaround solution since you are using Imagick, in the fact that you can do some image math. Using the borderImage function simply add a border to each image up to 1 pixel larger than the image you have uploaded. Since the add_image_size() crops from center by default that should trigger the resize to the size you need and solve the issue.

For example you want dimensions of 1920 x 1080. Using Imagick getSize you can check the size of the image. Lets say it is exactly 1920 x 1080, but as we know this will not trigger the blur. So using borderImage we add a 1 pixel white border to the image. This should trigger wordpress to resize the image to 1920 x 1080 as long as crop is set to true.

If the image is smaller but relatively close to the needed size we just make the border lets say 10 pixels and so on to fill in the size, and let wordpress crop from center. This will work with proportional images only.

If the image is a good bit smaller, lets say 200 x 800, we should consider adding a different add_image_size() option to handle smaller images. If you need to forge ahead then we can either use Imagick to "expand" our image to the needed proportions OR we can create a new white Imagick canvas the size we need and overlay the image to top left such that we have whitespace below and to the right of our new image. You would then use the add_image_size crop parameter to cutoff the excess white. For example, add_image_size('background-image-blurred', 1920, 1080, array('left','top')) to set the crop starting from the top left of the image.

Kings answered 27/12, 2015 at 23:51 Comment(1)
Good idea, but I think it's creating a lot of steps just to get around some WordPress limitations. I upvoted for the effort!Scantling
S
0

i think you need to trick a bit why you are not trying to have image of the size 1 x 1 or 5 X 5 so every time regardless what ever the image would be it will generate the Thumbnail for that image.

and upon the 'wp_generate_attachment_metadata' Hack the Stuff and generate the image from the original image and replace the 1 x 1 image with the Blured image which you want there off cource you need to trick over the filter in case you are using 'background-image-blurred' at any places for display or other computation purpose.

Hope you got the idea i think it's a little bit hack but should work proper.

Shotwell answered 22/12, 2015 at 10:33 Comment(0)
A
0

What about focusing on the wp_handle_upload() function?

Here is an example of how it was used to blur images.

Anabelle answered 22/12, 2015 at 16:29 Comment(4)
Yeah good tip. Do you know if wp_handle_upload is called before wp_generate_attachment_metadata?Scantling
What if you uploaded the orig img with wp_handle_upload()... blurred it... and then called wp_generate_attachment_metadata on the blurred img?Anabelle
Did you try my suggestion?Anabelle
Been trying it, but it's hard to translate it to my situation (as a filter for all uploads). This is not really a direct answer, it's more of a suggestion, helpful but I was hoping for something more verbose.Scantling
N
0

An outside-the-box suggestion: since you are only blurring the image and not making any other changes to the image, why not let CSS do the legwork instead of creating another image on the server?

.heroBackgroundImage {
    background: url('uploadedimage.jpg'); 
    -webkit-filter: blur(8px);
    -moz-filter: blur(8px); 
    -o-filter: blur(8px); 
    -ms-filter: blur(8px); 
    filter: blur(8px);
    -webkit-transform: translate3d(0,0,0);
}

Save the server effort; let it be handled on the client side.

EDIT: Just saw your comment about it can't be CSS. Added webkit-transform to move effect to the GPU, making it render more efficiently. Otherwise, still saving for posterity.

Niela answered 28/12, 2015 at 23:40 Comment(0)
P
0

The example you are referring to makes use of wordpress filters.

The problem is: A wordpress filter is always bound to a certain image resolution and therefore you cannot have the image processed regardless of its resolution.

My recommendation is that you avoid filters. Instead pre-process the image at an earlier point in time. Please find below a sample code that will blur the image initially before handing over control to wordpress:

    // perform new upload
    include_once( ABSPATH . 'wp-admin/includes/image.php' );
    $imagetype = end(explode('/', getimagesize($imageurl)['mime']));
    $uniq_name = date('dmY').''.(int) microtime(true); 
    $filename = $uniq_name.'.'.$imagetype;

    $uploaddir = wp_upload_dir();
    $uploadfile = $uploaddir['path'] . '/' . $filename;
    $contents= file_get_contents($imageurl);
    $savefile = fopen($uploadfile, 'w');
    fwrite($savefile, $contents);
    fclose($savefile);

    // apply blur
    $image_resource = new Imagick( $uploadfile );
    $image_resource->resizeImage(500, 500, null, 1);
    $image_resource->blurImage( 40, 20 );
    $image_data = pathinfo( $uploadfile );

    $new_filename = $image_data['filename'] . '-blur.' . $image_data['extension'];
    $blur_image_path = str_replace($image_data['basename'], $new_filename, $uploadfile);
    if ( ! $image_resource->writeImage( $blur_image_path ) ) {
        unlink( $uploadfile );
        return ""; // fixme
    }

    unlink( $uploadfile );

    $wp_filetype = wp_check_filetype(basename($filename), null );
    $attachment = array(
        'post_mime_type' => $wp_filetype['type'],
        'post_title' => $imageurlHash,
        'post_content' => '',
        'post_status' => 'inherit'
    );

    $attach_id = wp_insert_attachment( $attachment, $blur_image_path );
    $imagenew = get_post( $attach_id );
    $fullsizepath = get_attached_file( $imagenew->ID );
    $attach_data = wp_generate_attachment_metadata( $attach_id, $fullsizepath );
    wp_update_attachment_metadata( $attach_id, $attach_data ); 
Perforated answered 22/10, 2019 at 11:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.