Manipulating images in PHP isn’t all that difficult. WordPress offers some functions to make it easier, and integrating with the WordPress uploader isn’t at all difficult, really.

For example, let’s say your theme needed a black and white version of the images, sized at 100×100, and cropped to that size. This might be suitable for Next/Previous images to use for a gallery, perhaps…

When black and white bears attack...

First, we need to create the image size itself:

add_action('after_setup_theme','themename_bw_size');
function themename_bw_size() {
	add_image_size('themename-bw-image', 100, 100, true);
}

This simple function hooks into the after_setup_theme hook, which is loaded after the theme’s functions.php file is loaded. The function calls add_image_size to add a new image size for the uploader to create for each image that is uploaded into the WordPress media library. The size is specified as 100×100 with hard cropping.

Now that is easy enough, but we need to make the image black and white. To do this, we’ll hook into the wp_generate_attachment_metadata filter. This filter is called after the images have been resized and saved, but before the metadata about those resized images has been added to the attachment post. We’re not actually using this filter as a “filter”, but we need some of the meta data so this is as good a place to hook as any, as long as we remember to return the metadata again.

add_filter('wp_generate_attachment_metadata','themename_bw_filter');
function themename_bw_filter($meta) {
	$file = wp_upload_dir();
	$file = trailingslashit($file['path']).$meta['sizes']['themename-bw-image']['file'];
	list($orig_w, $orig_h, $orig_type) = @getimagesize($file);
	$image = wp_load_image($file);
	imagefilter($image, IMG_FILTER_GRAYSCALE);
	switch ($orig_type) {
		case IMAGETYPE_GIF:
			imagegif( $image, $file );
			break;
		case IMAGETYPE_PNG:
			imagepng( $image, $file );
			break;
		case IMAGETYPE_JPEG:
			imagejpeg( $image, $file );
			break;
	}
	return $meta;
}

Let’s break this down piece by piece:

$file = wp_upload_dir();
$file = trailingslashit($file['path']).$meta['sizes']['themename-bw-image']['file'];

This bit of code gets our upload directory path, makes sure it’s trailing slashed, then appends the filename of our themename-bw-image file to it. Thus, after this is run, $file contains the full local path to the image we want to make black and white.

list($orig_w, $orig_h, $orig_type) = @getimagesize($file);

This line of code simply gets the image size and type into some variables. We really only need the type for later, but this is an easy way to do it.

$image = wp_load_image($file);

The wp_load_image function is a handy one. It reads in the image and returns a PHP image resource for us to work with.

imagefilter($image, IMG_FILTER_GRAYSCALE);

This simply applies the grayscale filter to the image resource, making it black and white.

switch ($orig_type) {
	case IMAGETYPE_GIF:
		imagegif( $image, $file );
		break;
	case IMAGETYPE_PNG:
		imagepng( $image, $file );
		break;
	case IMAGETYPE_JPEG:
		imagejpeg( $image, $file );
		break;
}

Finally, we save the image back to the same file, overwriting the color one with the black and white one.

return $meta;

Since we used a filter, it’s important that we don’t leave out the final return to return the original information we were passed in, so that the metadata gets properly stored in the attachment post. We didn’t actually modify the metadata here, though we could.

Pretty simple to manipulate images in this way. You could apply one of the many other types of filters available, or change all the images into pictures of bears, or whatever you like, really.

Shortlink:

45 Comments

  1. I didn’t know you could do this! You are awesome! Now I’m going to pretend I’m a photographer and start applying filters right on WordPress. Thanks!

  2. Nice, saved to snippetsapp for future use. Thanks for sharing Otto 🙂

  3. I’ve gotten a few responses back that didn’t seem to get the gist of how to do this sort of thing for something other than making images black and white. The above code only has *one* line that pertains to black and white. You can actually do anything there.

    This code, for example, will do the same basic thing and make sepia-toned images. Credit for the sepia filter code goes to http://hanswestman.se/2011/07/some-image-filters-for-php-gd/.

    function image_filter_sepia(&$image){
    	$width = imagesx($image);
    	$height = imagesy($image);
    	for($_x = 0; $_x < $width; $_x++){
    		for($_y = 0; $_y < $height; $_y++){
    			$rgb = imagecolorat($image, $_x, $_y);
    			$r = ($rgb>>16)&0xFF;
    			$g = ($rgb>>8)&0xFF;
    			$b = $rgb&0xFF;
    
    			$y = $r*0.299 + $g*0.587 + $b*0.114;
    			$i = 0.15*0xFF;
    			$q = -0.001*0xFF;
    
    			$r = $y + 0.956*$i + 0.621*$q;
    			$g = $y - 0.272*$i - 0.647*$q;
    			$b = $y - 1.105*$i + 1.702*$q;
    
    			if($r<0||$r>0xFF){$r=($r<0)?0:0xFF;}
    			if($g<0||$g>0xFF){$g=($g<0)?0:0xFF;}
    			if($b<0||$b>0xFF){$b=($b<0)?0:0xFF;}
    
    			$color = imagecolorallocate($image, $r, $g, $b);
    			imagesetpixel($image, $_x, $_y, $color);
    		}
    	}
    }
    
    add_filter('wp_generate_attachment_metadata','themename_sepia_filter');
    function themename_sepia_filter($meta) {
    	$file = wp_upload_dir();
    	$file = trailingslashit($file['path']).$meta['sizes']['themename-sepia-image']['file'];
    	list($orig_w, $orig_h, $orig_type) = @getimagesize($file);
    	$image = wp_load_image($file);
    	image_filter_sepia($image);
    	switch ($orig_type) {
    		case IMAGETYPE_GIF:
    			imagegif( $image, $file );
    			break;
    		case IMAGETYPE_PNG:
    			imagepng( $image, $file );
    			break;
    		case IMAGETYPE_JPEG:
    			imagejpeg( $image, $file );
    			break;
    	}
    	return $meta;
    }
    

    So feel free to play with the code and try new filters and things. It’s a example, not a final version.

  4. Thank you very much, exactly what I was looking for. It works great on the media page, but when I try to upload a featured image I get the following error: “imagefilter() expects parameter 1 to be resource, string given…” Apparently there is a difference in uploading through the media page and uploading a featured image of a post. Any idea how to fix this?

    • I didn’t put any error handling in, since it was just an example. You’d probably want to make sure that the resulting file exists, since it won’t get created if the uploaded image is smaller than the size you’re wanting.

      So something like:

      if (!file_exists($file)) return $meta;
      

      Would work after the $file is defined. This makes it fail when the file isn’t there and can’t be loaded.

  5. Very slick Otto! I got the next beverage 🙂

    It would be interesting to see what this looked like as an option in the image upload process.

    Cheers!

  6. […] credits f&#959r th&#1110&#1109 awesome trick goes t&#959 Otto. Th&#1110&#1109 post &#1110&#1109 presented b&#1091 th&#1077 leader &#1110n printing services, Next […]

  7. Great article and I made this worked in my theme with no problem, thanks Otto.
    But what I really want to do is making a grayscale of the fullsize.
    When I set the same size as the original image in “add_image_size”,
    it returns function error, which is probably the same as Wouter Den Boer mentioned above.
    Should I add “if (!file_exists($file)) return;” to somewhere?
    I wonder if you could give me any advice..
    Thanks!

  8. Hello there,

    I am using this solution but I am encountering an error.

    I have created two different image sizes one called “navigation-thumbnail” and one called “bw-navigation-thumbnail”.

    Both image sizes are exactly the same size (219 * 144).

    When I call both through (for a rollover effect) both images are black and white.

    When inspecting the images that are been pulled through both images have “-219×144” at the end of the file name.

    One way of fixing it is to change the file size when creating the add image. So if I change one to “220, 145” it works, but this isn’t the ideal solution as I want the hover to be the same size as the colour image.

    Do you have any explanation of why this happening and a solution of how to stop it?

    Thanks in advance.

    Matthew Burrows.

    • Image sizes have to be unique. You can’t have two of the same size.

      You could use a slightly different method, and create a different file with like -bw added to the filename or something, thus making a separate file, then use that in your hover code.

      • Otto,
        Please, can you provide tutorial, or explain ” slightly different method” to create B&W images than rollover the original color? This is my ? for long time in WP. It is just what Matthew Burrows ask for, so 2 of us now:)

    • If you want to use the images for a rollover effect, I think the best option it’s to create an sprite with the coloured and the b&w version on the same image. You can apply the rollover with CSS position. The images will weight more, but there won’t be a delay when the user hovers the image… if you decide to go this way, it’s best if the unactive image it’s above the hovered version.

  9. Hi Otto,

    Thanks for your swift reply, I never knew you couldn’t have two images the same size (never tried it before now), so thanks for pointing that out.

    I will take a look at implementing your method and post the code if its successful so others can use it as well if they are having the same problems.

    Thanks once again.

    Kind regards,

    Matt.

  10. OTTO,
    Thanks, great subject.
    Now, i would like to ask you for solution to this:
    CSS B&W featured image , than on mouse over : original in color.
    Can you help on this?
    THX

  11. […] to get the effect to work, which required an image editing software like Photoshop. Thankfully, Otto just wrote a great article on a way that you can use some built in WordPress functions and a little […]

  12. I’ve copied your code exactly but cannot get a black and white version to appear for the life of me! Both images display in color…is there a certain PHP library that needs to be installed that I can check for? I’m using MAMP Version 2.0.1.

  13. Nevermind….turns out that there needs to be some sort of check to make sure that the generated thumbnail exists (in case it’s uploaded at the correct size and therefore WordPress uses the standard size, not referencing it through the sizes array).

    Thanks anyway!

  14. […] WordPress Images with a Plugin: ImageFX October 4, 2011, 8:12 pm My post about how to customize WordPress images with tricks like greyscale and such got me lots of feedback and such, so I figured I might as well […]

  15. Thanks for this code.

    There is a small bug when using it with older posts combined with setting images to be organised by year/month. To get around it I’ve been checking to see if the ‘basdir’ key is set in the array returned by wp_upload_dir(). If it is, that means year/months folders are on and from there grabbing the path info dir name from the file key from the passed in file metadata and building up the correct file path. Seems to work fine. I’ve used your script as a basis for making CSS sprites. Cheers.

  16. Hi Otto,

    This code is awesome. I was using it recently for a project but I noticed when resizing images with the Regenerate plugin I was getting an error message because some images were smaller then image size(my size was bigger then 100 by 100). Here was just some code that fixed this issue.

    if($orig_w >= 100 && $orig_h >= 100) {
    $image = wp_load_image($file);
    image_filter_sepia($image);
    switch ($orig_type) {
    case IMAGETYPE_GIF:
    imagegif( $image, $file );
    break;
    case IMAGETYPE_PNG:
    imagepng( $image, $file );
    break;
    case IMAGETYPE_JPEG:
    imagejpeg( $image, $file );
    break;
    }

    Thanks again for the Awesome code.

  17. I’m getting the same error:

    Warning: imagefilter() expects parameter 1 to be resource, string given

    But I’m getting it because the Page that I’m uploading the image to was published last month, and wp_upload_dir() returns the current month’s upload path, so it looks like the image doesn’t exist.

    Any suggestions on how I might go about fixing this? I’d love to be able to generate b/w thumbs for both posts and pages, regardless of when they were published.

    Thanks!

    • I fixed it by doing this:


      function bw_filter($meta) {
      $path = wp_upload_dir();
      /* get dirname of file to get correct date folder */
      $subdir = trailingslashit(dirname($meta['file']));
      /* get basedir on path to get uploads folder */
      $file = trailingslashit($path['basedir']).$subdir.$meta['sizes']['bw-image']['file'];
      list($orig_w, $orig_h, $orig_type) = @getimagesize($file);
      $image = wp_load_image($file);
      imagefilter($image, IMG_FILTER_GRAYSCALE);
      switch ($orig_type) {
      case IMAGETYPE_GIF:
      imagegif( $image, $file );
      break;
      case IMAGETYPE_PNG:
      imagepng( $image, $file );
      break;
      case IMAGETYPE_JPEG:
      imagejpeg( $image, $file );
      break;
      }
      return $meta;
      }

      add_filter('wp_generate_attachment_metadata','bw_filter');

  18. Thanks for this. It came in handy on a recent project.

    I was having issues with it at first. The issue I had was if you uploaded an image smaller than the image size, you would get an error (undefined index). So I added a conditional to check if that image size was available, and if it wasn’t it used the original image: https://gist.github.com/2952049

  19. […] credits for this awesome trick goes to Otto. This post is presented by the leader in printing services, Next Day Flyers. As the name indicates […]

  20. Hi Otto

    Thank you for this, it’s fantastic creating a b/w thumbnail for me. However I’m having trouble with watermarking the full colour images. All plugins I have tried removed the b/w filter when you run the plugin. I cannot stop this from happening and I cannot figure out a way to automate adding a watermark using the function.php and there is no documentation online on how to do this with WordPress…

    Do you have any ideas how to do this?

    Thanks!

  21. Hi Otto,
    Great stuff! but is it possible to apply a filter to an image url directly instead of applying it to an image when your uploading it.
    I have a file upload field and a select list and would like to give the user the option of what filter to use.

    something like which would output a new url for the new manipulated image file.

    Thanks!

  22. thanks a lot for the code
    i have a question. is it possible to apply b&w only for post thumbs and not the original image?
    thank you again

  23. Hi Otto, can you give an example of how to modify the metadata before returning it? Like for example let’s say we changed the format of the file from jpg to gif to make a smaller thumbnail file. Would it be possible to write the new file path into the meta?

    Monty

  24. Figured it out . . . to modify the meta, add a line like $meta['sizes']['custom-size-name']['url'] = $newurl'; before return $meta

  25. For those who get more string errors when there are images that aren’t as big as the size defined and throw an error… I simply threw in a check for if the image exists a couple of times to make sure that all data passed to each function’s arguments are valid.

    add_filter('wp_generate_attachment_metadata','twentytwelve_blur_filter');
    function twentytwelve_blur_filter($meta) {
        $path = wp_upload_dir();
        $subdir = trailingslashit(dirname($meta['file']));
        /* get basedir on path to get uploads folder */
        $file = trailingslashit($path['basedir']).$subdir.$meta['sizes']['banner-image-high-blur']['file'];
        if ($meta['sizes']['banner-image-high-blur']) {
            list($orig_w, $orig_h, $orig_type) = @getimagesize($file);
            $image = wp_load_image($file);
    
            if ($image) {
                for ($i = 0; $i < 10; $i++) {
                    imagefilter($image, IMG_FILTER_GAUSSIAN_BLUR);
                }
    
                switch ($orig_type) {
                    case IMAGETYPE_GIF:
                        imagegif( $image, $file );
                        break;
                    case IMAGETYPE_PNG:
                        imagepng( $image, $file );
                        break;
                    case IMAGETYPE_JPEG:
                        imagejpeg( $image, $file, 95 );
                        break;
                }
            }
        }
    
        return $meta;
    }
    

    This also includes the fix for the subdirectory issue

  26. I tried to use IMG_FILTER_GAUSSIAN_BLUR filter, but I’d like to have a stronger effect. Apparently ImageMagick has a customisable nice gaussian blur, but I cant get it to work.

    Did I do any stupid mistake? I’m really lost here..
    This is what I changed:

    add_action('after_setup_theme','blur_images_size');
    function blur_images_size() {
    add_image_size('background-blur', 404, 404, false);
    }

    add_filter('wp_generate_attachment_metadata','blur_images_filter');
    function blur_images_filter($meta) {
    $file = wp_upload_dir();
    $file = trailingslashit($file['path']).$meta['sizes']['background-blur']['file'];
    if( $file ) {
    list($orig_w, $orig_h, $orig_type) = @getimagesize($file);
    $wpimage = wp_load_image($file);
    $image = new Imagick();
    $image->readImagefile($file);
    $image->gaussianBlurImage(5,3);
    $image->writeImage($file);

    return $meta;
    }
    }

    • 🙂 I worked it out myself.
      As it turns out, you don’t need wp_load_image() for image magick. Instead you can work with $file directly. Here’s my working code:
      (note that I also updated the function after Marksyzm’s suggested improvement (the comment above))


      add_action('after_setup_theme','blur_images_size');
      function blur_images_size() {
      add_image_size('background-blur', 1600, 1600, false);
      }

      add_filter('wp_generate_attachment_metadata','blur_images_filter');
      function blur_images_filter($meta) {
      $path = wp_upload_dir();
      $subdir = trailingslashit(dirname($meta['file']));
      /* get basedir on path to get uploads folder */
      $file = trailingslashit($path['basedir']).$subdir.$meta['sizes']['background-blur']['file'];
      if ($meta['sizes']['background-blur']) {
      list($orig_w, $orig_h, $orig_type) = @getimagesize($file);
      $image = wp_load_image($file);

      if ($image) {
      $image = new Imagick($file);
      $image->gaussianBlurImage(20,10);
      $image->writeImage($file);
      }
      }

      return $meta;
      }

  27. how to change the result image grayscale to spesific folder
    thanks

  28. wp_load_image is depreciated. How can I use wp get image editor instead?

  29. This usually won’t work if you’re running it on older existing images (for example, when using Regenerate Thumbnails) because wp_upload_dir() will only return the current year/month folder.

    To fix this, you need to get the path of the current attachment, not the current path for new files. Replace the first four lines with the following:

    add_filter(‘wp_generate_attachment_metadata’,’themename_bw_filter’, 10, 2);
    function themename_bw_filter($meta, $attachment_id) {
    $file_path = get_attached_file( $attachment_id );
    $file_dir = dirname( $file_path );
    $file = trailingslashit( $file_dir ) . $meta[‘sizes’][‘themename-bw-image’][‘file’];

    Otherwise, five years on, this still works great.

  30. […] All credits for this awesome trick goes to Otto. […]

  31. […] All credits for this awesome trick goes to Otto. […]

Leave a Reply

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Need to post PHP code? Wrap it in [php] and [/php] tags.