diff --git a/index.php b/index.php
index f9bef567b861e1be12ed3dcded7deca36613df30..96e05a2cb80c98179adf00d4ce65b2711b761eea 100644
--- a/index.php
+++ b/index.php
@@ -1,7 +1,8 @@
 <?php
 
-define( 'PHOTON__ALLOW_ANY_EXTENSION', 1 );
-define( 'PHOTON__ALLOW_QUERY_STRINGS', 2 );
+define( 'PHOTON__ALLOW_QUERY_STRINGS', 1 );
+define( 'PHOTON__DEFAULT_MAX_QUALITY', 89 );
+define( 'PHOTON__PNG_MAX_QUALITY', 80 );
 
 require dirname( __FILE__ ) . '/plugin.php';
 if ( file_exists( dirname( __FILE__ ) . '/../config.php' ) )
@@ -13,8 +14,10 @@ else if ( file_exists( dirname( __FILE__ ) . '/config.php' ) )
 $allowed_functions = apply_filters( 'allowed_functions', array(
 //	'q'           => RESERVED
 //	'zoom'        => global resolution multiplier (argument filter)
-	'h'           => 'setheight',       // done
-	'w'           => 'setwidth',        // done
+//	'quality'     => sets the quality of JPEG images during processing
+//	'strip        => strips JPEG images of exif, icc or all "extra" data (params: info,color,all)
+	'h'           => 'set_height',      // done
+	'w'           => 'set_width',       // done
 	'crop'        => 'crop',            // done
 	'resize'      => 'resize_and_crop', // done
 	'fit'         => 'fit_in_box',      // done
@@ -47,24 +50,37 @@ $remote_image_max_size = apply_filters( 'remote_image_max_size', 55 * 1024 * 102
 /* Array of domains exceptions
  * Keys are domain name
  * Values are bitmasks with the following options:
- * PHOTON__ALLOW_ANY_EXTENSION: Allow any extension (including none) in the path of the URL
  * PHOTON__ALLOW_QUERY_STRINGS: Append the string found in the 'q' query string parameter as the query string of the remote URL
  */
 $origin_domain_exceptions = apply_filters( 'origin_domain_exceptions', array() );
 
 // You can override this by defining it in config.php
 if ( ! defined( 'PHOTON__UPSCALE_MAX_PIXELS' ) )
-	define( 'PHOTON__UPSCALE_MAX_PIXELS', 1000 );
+	define( 'PHOTON__UPSCALE_MAX_PIXELS', 2000 );
+
+// Allow smaller upscales for GIFs, compared to the other image types
+if ( ! defined( 'PHOTON__UPSCALE_MAX_PIXELS_GIF' ) )
+	define( 'PHOTON__UPSCALE_MAX_PIXELS_GIF', 1000 );
 
 require dirname( __FILE__ ) . '/libjpeg.php';
 
 // Implicit configuration
-if ( file_exists( '/usr/local/bin/optipng' ) )
+if ( file_exists( '/usr/local/bin/optipng' ) && ! defined( 'DISABLE_IMAGE_OPTIMIZATIONS' ) )
 	define( 'OPTIPNG', '/usr/local/bin/optipng' );
 else
 	define( 'OPTIPNG', false );
 
-if ( file_exists( '/usr/local/bin/jpegoptim' ) )
+if ( file_exists( '/usr/local/bin/pngquant' ) && ! defined( 'DISABLE_IMAGE_OPTIMIZATIONS' ) )
+	define( 'PNGQUANT', '/usr/local/bin/pngquant' );
+else
+	define( 'PNGQUANT', false );
+
+if ( file_exists( '/usr/local/bin/cwebp' ) && ! defined( 'DISABLE_IMAGE_OPTIMIZATIONS' ) )
+	define( 'CWEBP', '/usr/local/bin/cwebp' );
+else
+	define( 'CWEBP', false );
+
+if ( file_exists( '/usr/local/bin/jpegoptim' ) && ! defined( 'DISABLE_IMAGE_OPTIMIZATIONS' ) )
 	define( 'JPEGOPTIM', '/usr/local/bin/jpegoptim' );
 else
 	define( 'JPEGOPTIM', false );
@@ -103,8 +119,8 @@ function zoom( $arguments, $function_name, $image ) {
 	$h = $image->getimageheight();
 
 	switch ( $function_name ) {
-		case 'setheight' :
-		case 'setwidth' :
+		case 'set_height' :
+		case 'set_width' :
 			$new_arguments = $arguments * $zoom;
 			if ( substr( $arguments, -1 ) == '%' )
 				$new_arguments .= '%';
@@ -176,7 +192,7 @@ function crop( &$image, $args ) {
 }
 
 /**
- * setheight - ( "h" function via the uri ) - resize the image to an explicit height, maintaining its aspect ratio
+ * set_height - ( "h" function via the uri ) - resize the image to an explicit height, maintaining its aspect ratio
  *
  * @param (resource)image the source gd image resource
  * @param (string)args "/^[0-9]+%?$/" the new height in pixels, or as a percentage if suffixed with an %
@@ -184,7 +200,7 @@ function crop( &$image, $args ) {
  *
  * @return (resource) the resulting gs image resource
  **/
-function setheight( &$image, $args, $upscale = false ) {
+function set_height( &$image, $args, $upscale = false ) {
 	$w = $image->getimagewidth();
 	$h = $image->getimageheight();
 
@@ -199,7 +215,7 @@ function setheight( &$image, $args, $upscale = false ) {
 	// New height is greater than original image, but we don't have permission to upscale
 	if ( $new_height > $h && ! $upscale )
 		return;
-	// Sane limit when upscaling, defaults to 1000
+	// Sane limit when upscaling
 	if ( $new_height > $h && $upscale && $new_height > PHOTON__UPSCALE_MAX_PIXELS )
 		return;
 
@@ -213,7 +229,7 @@ function setheight( &$image, $args, $upscale = false ) {
 }
 
 /**
- * setwidth - ( "w" function via the uri ) - resize the image to an explicit width, maintaining its aspect ratio
+ * set_width - ( "w" function via the uri ) - resize the image to an explicit width, maintaining its aspect ratio
  *
  * @param (resource)image the source gd image resource
  * @param (string)args "/^[0-9]+%?$/" the new width in pixels, or as a percentage if suffixed with an %
@@ -221,7 +237,7 @@ function setheight( &$image, $args, $upscale = false ) {
  *
  * @return (resource) the resulting gs image resource
  **/
-function setwidth( &$image, $args, $upscale = false ) {
+function set_width( &$image, $args, $upscale = false ) {
 	$w = $image->getimagewidth();
 	$h = $image->getimageheight();
 
@@ -236,7 +252,7 @@ function setwidth( &$image, $args, $upscale = false ) {
 	// New height is greater than original image, but we don't have permission to upscale
 	if ( $new_width > $w && ! $upscale )
 		return;
-	// Sane limit when upscaling, defaults to 1000
+	// Sane limit when upscaling
 	if ( $new_width > $w && $upscale && $new_width > PHOTON__UPSCALE_MAX_PIXELS )
 		return;
 
@@ -278,7 +294,7 @@ function fit_in_box( &$image, $args ) {
 /**
  * resize_and_crop - ("resize" function via the uri) - originally by Alex M.
  *
- * Differs from setwidth, setheight, and crop in that you provide a width/height and it resizes to that and then crops off excess
+ * Differs from set_width, set_height, and crop in that you provide a width/height and it resizes to that and then crops off excess
  *
  * @param (resource) image the source gd image resource
  * @param (string) args "w,h" width,height in pixels
@@ -298,23 +314,29 @@ function resize_and_crop( &$image, $args ) {
 	if ( 0 == $end_w || 0 == $end_h )
 		return;
 
+	if ( $end_w > $w && $end_w > PHOTON__UPSCALE_MAX_PIXELS )
+		return;
+
+	if ( $end_h > $h && $end_h > PHOTON__UPSCALE_MAX_PIXELS )
+		return;
+
 	$ratio_orig = $w / $h;
 	$ratio_end = $end_w / $end_h;
 
 	// If the original and new images are proportional (no cropping needed), just do a standard resize
 	if ( $ratio_orig == $ratio_end )
-		setwidth( $image, $end_w, true );
+		set_width( $image, $end_w, true );
 
 	// If we need to crop off the sides
 	elseif ( $ratio_orig > $ratio_end ) {
-		setheight( $image, $end_h, true );
+		set_height( $image, $end_h, true );
 		$x = floor( ( $image->getimagewidth() - $end_w ) / 2 );
 		crop( $image, "{$x}px,0px,{$end_w}px,{$end_h}px" );
 	}
 
 	// If we need to crop off the top/bottom
 	elseif ( $ratio_orig < $ratio_end ) {
-		setwidth( $image, $end_w, true );
+		set_width( $image, $end_w, true );
 		$y = floor( ( $image->getimageheight() - $end_h ) / 2 );
 		crop( $image, "0px,{$y}px,{$end_w}px,{$end_h}px" );
 	}
@@ -640,10 +662,102 @@ function do_a_filter( $function_name, $arguments ) {
 	}
 }
 
-function photon_cache_headers( $expires=63115200 ) {
+function photon_cache_headers( $image_url, $content_type = 'image/jpeg', $expires = 63115200 ) {
+	header( 'Last-Modified: ' . gmdate( 'D, d M Y H:i:s', time() ) . ' GMT' );
 	header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', time() + $expires ) . ' GMT' );
 	header( 'Cache-Control: public, max-age='.$expires );
 	header( 'X-Content-Type-Options: nosniff' );
+	header( 'Link: <' . $image_url . '>; rel="canonical"' );
+	header( 'Content-Type: ' . $content_type );
+	// animated webp images are not much smaller than GIFs and take an age to convert,
+	// ignore them and do not affect caching with the Vary header.
+	if ( 'image/gif' != $content_type )
+		header( 'Vary: Accept' );
+}
+
+function serve_file( $url, $content_type, $filename, $bytes_saved ) {
+	photon_cache_headers( $url, $content_type );
+	$filesize = filesize( $filename );
+	header( 'ETag: "' . substr( md5( $filesize . '.' . time() ), 0, 16 ) . '"' );
+	header( 'Content-Length: ' . $filesize );
+	if ( defined( 'DISABLE_IMAGE_OPTIMIZATIONS' ) ) {
+		header( "X-Optim-Disabled: true" );
+	} else {
+		header( 'X-Bytes-Saved: ' . $bytes_saved );
+	}
+	$fp = fopen( $filename, 'r' );
+	register_shutdown_function( 'unlink', $filename );
+	fpassthru( $fp );
+}
+
+/**
+ * Compresses the PNG image using the best option available
+ *
+ * @param string $filename The file name of the temp image to compress
+ * @param int $quality The requested quality of the image
+ *
+ * @return string The content type of the final image (png or webp)
+ **/
+function compress_image_png( $filename, $quality ) {
+	$content_type = 'image/png';
+	if ( isset( $_GET['quality'] ) && 100 == intval( $_GET['quality'] ) ) {
+		if ( false !== OPTIPNG ) {
+			exec( OPTIPNG . " $filename" );
+		} else if ( false !== CWEBP && ( defined( 'CWEBP_PNG' ) && true === CWEBP_PNG ) &&
+				isset( $_SERVER['HTTP_ACCEPT'] ) && false !== strpos( $_SERVER['HTTP_ACCEPT'], 'image/webp' ) ) {
+			$content_type = 'image/webp';
+			exec( CWEBP . " -quiet --lossless $filename -o $filename" );
+		}
+	} else {
+		if ( false !== CWEBP && ( defined( 'CWEBP_PNG' ) && true === CWEBP_PNG ) &&
+			isset( $_SERVER['HTTP_ACCEPT'] ) && false !== strpos( $_SERVER['HTTP_ACCEPT'], 'image/webp' ) ) {
+			$content_type = 'image/webp';
+			exec( CWEBP . " -quiet -q $quality -alpha_q 100 $filename -o $filename" );
+		} else if ( false !== PNGQUANT ) {
+			exec( PNGQUANT . ' --speed 5 --quality=' . $quality . "-100 -f -o $filename $filename" );
+			if ( false !== OPTIPNG ) exec( OPTIPNG . " -o1 $filename" );
+		} else {
+			if ( false !== OPTIPNG ) exec( OPTIPNG . " $filename" );
+		}
+	}
+	return $content_type;
+}
+
+/**
+ * Compresses the JPEG image using the best option available
+ *
+ * @param string $filename The file name of the temp image to compress
+ * @param resource $image The GraphicsMagick image resource
+ * @param int $quality The requested quality of the image
+ *
+ * @return string The content type of the final image (jpeg or webp)
+ **/
+function compress_image_jpg( $filename, $image, $quality ) {
+	$content_type = 'image/jpeg';
+	// Convert to WEBP if the following hold:
+	// 1. WEBP image processing for JPEGs is enabled.
+	// 2. The client is advertising support for WEBP in the accept header.
+	// 3. The requested quality is not 100%, as we need to honour sending images at their original
+	//    visual quality and WEBP images at high quality are larger than their JPEG counterparts.
+	if ( false !== CWEBP && ( defined( 'CWEBP_JPEG' ) && true === CWEBP_JPEG ) &&
+		isset( $_SERVER['HTTP_ACCEPT'] ) && false !== strpos( $_SERVER['HTTP_ACCEPT'], 'image/webp' ) &&
+		! ( isset( $_GET['quality'] ) && 100 == intval( $_GET['quality'] ) ) ) {
+		$content_type = 'image/webp';
+		exifrotate( $filename, $image, true );
+		if ( Gmagick::IMGTYPE_GRAYSCALE == $image->getimagetype() ) {
+			exec( CWEBP . " --lossless -quiet -m 2 -q $quality -o $filename $filename" );
+		} else {
+			exec( CWEBP . " -quiet -m 2 -q $quality -o $filename $filename" );
+		}
+	} else if ( false !== JPEGOPTIM ) {
+		$strip = false;
+		if ( isset( $_GET['strip'] ) ) {
+			$strip = $_GET['strip'];
+			exifrotate( $filename, $image, $strip );
+		}
+		jpegoptim( $filename, $strip );
+	}
+	return $content_type;
 }
 
 $parsed = parse_url( $_SERVER['REQUEST_URI'] );
@@ -651,22 +765,19 @@ $exploded = explode( '/', $_SERVER['REQUEST_URI'] );
 $origin_domain = strtolower( $exploded[1] );
 $origin_domain_exception = array_key_exists( $origin_domain, $origin_domain_exceptions ) ? $origin_domain_exceptions[$origin_domain] : 0;
 
-$scheme = 'http' . ( array_key_exists( 'ssl', $_GET ) ? 's' : '' ) . '://';
+$scheme = 'http' . ( array_key_exists( 'HTTPS', $_SERVER ) ? 's' : '' ) . '://';
 parse_str( ( empty( $parsed['query'] ) ? '' : $parsed['query'] ),  $_GET  );
 
 $ext = strtolower( pathinfo( $parsed['path'], PATHINFO_EXTENSION ) );
 
-if ( ! in_array( $ext, $allowed_types ) && !( $origin_domain_exception & PHOTON__ALLOW_ANY_EXTENSION ) )
-	httpdie( '400 Bad Request', 'Error 0001. The type of image you are trying to process is not allowed.' );
-
 $url = $scheme . substr( $parsed['path'], 1 );
 $url = preg_replace( '/#.*$/', '', $url );
 $url = apply_filters( 'url', $url );
 
-if ( isset( $parsed['query'] ) ) {
+if ( isset( $_GET['q'] ) ) {
 	if ( $origin_domain_exception & PHOTON__ALLOW_QUERY_STRINGS ) {
-		$url .= '?' . preg_replace( '/#.*$/', '', (string) $parsed['query'] );
-		unset( $parsed['query'] );
+		$url .= '?' . preg_replace( '/#.*$/', '', (string) $_GET['q'] );
+		unset( $_GET['q'] );
 	} else {
 		httpdie( '400 Bad Request', "Sorry, the parameters you provided were not valid" );
 	}
@@ -690,6 +801,13 @@ if ( 'GIF' === substr( $raw_data, 0, 3 ) ) {
 	require dirname( __FILE__ ) . '/libgif.php';
 	$image = new Gif_Image( $raw_data );
 	$type = 'gif';
+	if ( 0 === strlen( $_SERVER['QUERY_STRING'] ) ) {
+		do_action( 'bump_stats', 'image_gif' );
+		photon_cache_headers( $url, 'image/gif' );
+		header( 'ETag: "' . substr( md5( $raw_data_size . '.' . time() ), 0, 16 ) . '"' );
+		header( 'Content-Length: ' . $raw_data_size );
+		die( $raw_data );
+	}
 } else {
 	try {
 		$image = new Gmagick();
@@ -705,10 +823,17 @@ if ( ! in_array( $type, $allowed_types ) )
 
 if ( $type == 'jpeg' )
 	$quality = get_jpeg_quality( $raw_data, $raw_data_size );
+else if ( $type == 'png' )
+	$quality = PHOTON__PNG_MAX_QUALITY;
 else
-	$quality = 90;
+	$quality = PHOTON__DEFAULT_MAX_QUALITY;
 unset( $raw_data );
 
+if ( isset( $_GET['quality'] ) )
+	$quality = min( max( intval( $_GET['quality'] ), 20 ), $quality );
+else
+	$quality = min( PHOTON__DEFAULT_MAX_QUALITY, $quality );
+
 try {
 	// Run through all uri supplied functions which are valid and allowed
 	foreach( $_GET as $function_name => $arguments ) {
@@ -722,50 +847,37 @@ try {
 
 	switch ( $type ) {
 		case 'png':
-			do_action( 'bump_stats', 'image_png' );
-			header( 'Content-Type: image/png' );
 			$image->setcompressionquality( $quality );
 			$tmp = tempnam( $tmpdir, 'OPTIPNG-' );
 			$image->write( $tmp );
 			$og = filesize( $tmp );
-			exec( OPTIPNG . " $tmp" );
+			$content_type = compress_image_png( $tmp, $quality );
 			clearstatcache();
 			$save = $og - filesize( $tmp );
+			serve_file( $url, $content_type, $tmp, $save );
+			do_action( 'bump_stats', 'image_png' . ( 'image/webp' == $content_type ? '_as_webp' : '' ) );
 			do_action( 'bump_stats', 'png_bytes_saved', $save );
-			$fp = fopen( $tmp, 'r' );
-			photon_cache_headers();
-			header( 'Content-Length: ' . filesize( $tmp ) );
-			header( 'X-Bytes-Saved: ' . $save );
-			unlink( $tmp );
-			fpassthru( $fp );
 			break;
 		case 'gif':
 			do_action( 'bump_stats', 'image_gif' );
-			if ( $image->process_image() ) {
-				photon_cache_headers();
-				header( 'Content-Type: image/gif' );
-				echo $image->get_imageblob();
+			if ( $image->process_image_functions( PHOTON__UPSCALE_MAX_PIXELS_GIF ) ) {
+				photon_cache_headers( $url, 'image/gif' );
+				echo $image->get_image_blob();
 			} else {
 				httpdie( '400 Bad Request', "Sorry, the parameters you provided were not valid" );
 			}
 			break;
 		default:
-			do_action( 'bump_stats', 'image_jpeg' );
-			header( 'Content-Type: image/jpeg' );
 			$image->setcompressionquality( $quality );
 			$tmp = tempnam( $tmpdir, 'JPEGOPTIM-' );
 			$image->write( $tmp );
 			$og = filesize( $tmp );
-			exec( JPEGOPTIM . " --all-progressive -p $tmp" );
+			$content_type = compress_image_jpg( $tmp, $image, $quality );
 			clearstatcache();
 			$save = $og - filesize( $tmp );
+			serve_file( $url, $content_type, $tmp, $save );
+			do_action( 'bump_stats', 'image_jpeg' . ( 'image/webp' == $content_type ? '_as_webp' : '' ) );
 			do_action( 'bump_stats', 'jpg_bytes_saved', $save );
-			$fp = fopen( $tmp, 'r' );
-			photon_cache_headers();
-			header( 'Content-Length: ' . filesize( $tmp ) );
-			header( 'X-Bytes-Saved: ' . $save );
-			unlink( $tmp );
-			fpassthru( $fp );
 			break;
 	}
 
diff --git a/libgif.php b/libgif.php
index e0921b4fc3b970997f0db0c130e6e31f96513592..90a05d234e1b1319f0b63041ef304ca9bf36b371 100644
--- a/libgif.php
+++ b/libgif.php
@@ -1,6 +1,7 @@
 <?php
 /*
- * Classes to handle the cropping, resizing and manipulation of animated GIF image files.
+ * Classes to manipulate animated GIF images.
+ * Maintained at: https://code.trac.wordpress.org/browser/photon/libgif.php
 */
 
 if ( ! class_exists( 'Gif_Frame' ) ) {
@@ -18,22 +19,22 @@ if ( ! class_exists( 'Gif_Frame' ) ) {
 		private $_image;
 
 		function __construct( $lc_mod, $palette, $image, $head, $box_dims, $gr_mod ) {
-			$this->pos_x    = $box_dims[0];
-			$this->pos_y    = $box_dims[1];
-			$this->width    = $box_dims[2];
-			$this->height   = $box_dims[3];
+			$this->pos_x   = $box_dims[0];
+			$this->pos_y   = $box_dims[1];
+			$this->width   = $box_dims[2];
+			$this->height  = $box_dims[3];
 
-			$this->lc_mod   = $lc_mod;
-			$this->gr_mod   = $gr_mod;
-			$this->palette  = $palette;
+			$this->lc_mod  = $lc_mod;
+			$this->gr_mod  = $gr_mod;
+			$this->palette = $palette;
 
 			if ( strlen( $gr_mod ) == 8 )
-				$this->transp   = ord( $gr_mod[3] ) & 1 ? 1 : 0;
+				$this->transp = ord( $gr_mod[3] ) & 1 ? 1 : 0;
 			else
-				$this->transp   = 0;
+				$this->transp = 0;
 
-			$this->head     = $head;
-			$this->image    = $image;
+			$this->head    = $head;
+			$this->image   = $image;
 		}
 
 		public function __set( $name, $value ) {
@@ -70,6 +71,7 @@ if ( ! class_exists( 'Gif_Image' ) ) {
 		private $crop_width = 0;
 		private $crop_height = 0;
 		private $crop = false;
+		private $fit = false;
 		private $resize_ratio = Array( 0, 0 );
 		private $frame_count = 0;
 		private $au = 0;
@@ -82,8 +84,10 @@ if ( ! class_exists( 'Gif_Image' ) ) {
 		private $dl_frms = Array();
 		private $pre_process_actions = Array();
 		private $post_process_actions = Array();
+		private $upscale_max_pixels = 1000;
+		private $zoom_enabled = true;
 
-		private static $pre_actions = Array( 'setheight', 'setwidth', 'crop', 'resize_and_crop', 'fit_in_box' );
+		private static $pre_actions = Array( 'set_height', 'set_width', 'crop', 'crop_offset', 'resize_and_crop', 'fit_in_box' );
 
 		const optimize = true;
 
@@ -99,17 +103,18 @@ if ( ! class_exists( 'Gif_Image' ) ) {
 		function __construct( $gif_data ) {
 			$this->gif = $gif_data;
 			$this->max_len = strlen( $gif_data );
-			if ( $this->max_len < 14 )
-				httpdie( '400 Bad Request', "unable to process the image data" );
-
+			if ( $this->max_len < 14 ) {
+				$this->error_and_die( '400 Bad Request', 'unable to process the image data' );
+			}
 			$this->gif_header = $this->get_bytes(13);
 			$this->parse_header();
 			$this->parse_frames();
 
 			$buffer_add = '';
 			while ( self::GIF_BLOCK_END != ord( $this->gif[ $this->ptr ] ) ) {
-				if ( $this->ptr >= $this->max_len )
-					httpdie( '400 Bad Request', "unable to process the image data" );
+				if ( $this->ptr >= $this->max_len ) {
+					$this->error_and_die( '400 Bad Request', 'unable to process the image data' );
+				}
 				switch ( ord( $this->gif[ $this->ptr + 1 ] ) ) {
 					case self::GIF_EXT_COMMENT:
 						$sum = 2;
@@ -180,8 +185,9 @@ if ( ! class_exists( 'Gif_Image' ) ) {
 							// invalid start header found, increment by 1 byte to 'sync' to the next header
 							$this->get_bytes( 1 );
 					}
-					if ( $this->ptr >= $this->max_len )
-						httpdie( '400 Bad Request', "unable to process the image header" );
+					if ( $this->ptr >= $this->max_len ) {
+						$this->error_and_die( '400 Bad Request', 'unable to process the image data' );
+					}
 				}
 				$this->g_mod = $buff;
 			}
@@ -198,8 +204,9 @@ if ( ! class_exists( 'Gif_Image' ) ) {
 						$this->frame_count--;
 						continue 2;
 					}
-					if ( $this->ptr >= $this->max_len )
-						httpdie( '400 Bad Request', "unable to process the image frames" );
+					if ( $this->ptr >= $this->max_len ) {
+						$this->error_and_die( '400 Bad Request', 'unable to process the image data' );
+					}
 					switch ( ord( $this->gif[ $this->ptr + 1 ] ) ) {
 						case self::GIF_EXT_GRAPHIC_CONTROL:
 							$this->gn_fld[]  = $this->gif[ $this->ptr + 3 ];
@@ -306,6 +313,17 @@ if ( ! class_exists( 'Gif_Image' ) ) {
 			return chr( $int & 255 ) . chr( ( $int & 0xFF00 ) >> 8 );
 		}
 
+		private function error_and_die( $result = '400 Bad Request', $message = '' ) {
+			if ( function_exists( 'imageresize_graceful_fail' ) ) {
+				imageresize_graceful_fail();
+			} else if ( function_exists( 'httpdie' ) ) {
+				httpdie( $result, $message );
+			} else {
+				header( "HTTP/1.1 $result" );
+				die( $message );
+			}
+		}
+
 		private function filter( &$image, $filter ) {
 			$args = explode( ',', $filter );
 			$filter = array_shift( $args );
@@ -461,14 +479,19 @@ if ( ! class_exists( 'Gif_Image' ) ) {
 			} else {
 				$offset_x = 0;
 				$offset_y = 0;
-				$n_width  = round( $this->frame_array[$index]->width * $this->resize_ratios[0] ) ? : 1;
-				$n_height = round( $this->frame_array[$index]->height * $this->resize_ratios[1] ) ? : 1;
+				if ( $this->fit ) {
+					$n_width  = $this->new_width;
+					$n_height = $this->new_height;
+				} else {
+					$n_width  = round( $this->frame_array[$index]->width * $this->resize_ratios[0] ) ? : 1;
+					$n_height = round( $this->frame_array[$index]->height * $this->resize_ratios[1] ) ? : 1;
+				}
 				$s_width  = $this->frame_array[$index]->width;
 				$s_height = $this->frame_array[$index]->height;
 			}
 
-			if ( 0 == $n_width )  $n_width   = 1;
-			if ( 0 == $n_height ) $n_height  = 1;
+			if ( 0 >= $n_width )  $n_width   = 1;
+			if ( 0 >= $n_height ) $n_height  = 1;
 			if ( 0 >= $s_width )  $s_width   = 1;
 			if ( 0 >= $s_height ) $s_height  = 1;
 
@@ -522,28 +545,26 @@ if ( ! class_exists( 'Gif_Image' ) ) {
 			$hd = $offset = 13 + pow( 2, ( ord( $str_img[10] ) & 7 ) + 1 ) * 3;
 			$palet = '';
 			$i_hd = 0;
-			$m_off = 0;
 
 			for ( $i = 13; $i < $offset; $i++ )
 				$palet .= $str_img[$i];
 
 			$str_max_len = strlen( $str_img );
-			if ( $this->frame_array[$index]->transp ) {
-				while ( self::GIF_EXT_GRAPHIC_CONTROL != ord( $str_img[ $offset + $m_off ] ) ) {
-					$m_off++;
-					if ( ( $offset + $m_off + 4 ) > $str_max_len )
-						httpdie( '400 Bad Request', "unable to reprocess the image frame" );
-				}
-				$str_img[ $offset + $m_off + 2 ] = $this->gn_fld[ $index ];
-				$str_img[ $offset + $m_off + 3 ] = $this->dl_frmf[ $index ];
-				$str_img[ $offset + $m_off + 4 ] = $this->dl_frms[ $index ];
-			}
-
 			while ( self::GIF_BLOCK_IMAGE_DESCRIPTOR != ord( $str_img[ $offset ] ) ) {
-				$offset++;
-				$i_hd++;
-				if ( ( $offset + 9 ) > $str_max_len )
-					httpdie( '400 Bad Request', "unable to reprocess the image frame" );
+				if ( self::GIF_EXT_GRAPHIC_CONTROL == ord( $str_img[ $offset + 1 ] ) &&
+					$this->frame_array[$index]->transp ) {
+						$str_img[ $offset + 3 ] = $this->gn_fld[ $index ];
+						$str_img[ $offset + 4 ] = $this->dl_frmf[ $index ];
+						$str_img[ $offset + 5 ] = $this->dl_frms[ $index ];
+				}
+				$sum = 2;
+				while ( 0x00 != ( $lc_i = ord( $str_img[ $offset + $sum ] ) ) )
+					$sum += $lc_i + 1;
+				$offset += ( $sum + 1 );
+				$i_hd += ( $sum + 1 );
+				if ( ( $offset + 10 ) > $str_max_len ) {
+					$this->error_and_die( '400 Bad Request', 'unable to reprocess the image frame' );
+				}
 			}
 
 			$str_img[ $offset + 1 ] = $this->frame_array[ $index ]->off_xy[0];
@@ -551,14 +572,14 @@ if ( ! class_exists( 'Gif_Image' ) ) {
 			$str_img[ $offset + 3 ] = $this->frame_array[ $index ]->off_xy[2];
 			$str_img[ $offset + 4 ] = $this->frame_array[ $index ]->off_xy[3];
 			$str_img[ $offset + 9 ] = chr( $str_img[ $offset + 9 ] | 0x80 | ( ord( $str_img[10] ) & 0x7 ) );
-			$ms1 = substr( $str_img, $hd, $i_hd + 10 );
 
+			$ms1 = substr( $str_img, $hd, $i_hd + 10 );
 			$ms1 = $this->frame_array[$index]->gr_mod . $ms1;
 
 			return $ms1 . $palet . substr( substr( $str_img, $offset + 10 ), 0, -1 );
 		}
 
-		private function setheight( $args, $upscale = false ) {
+		private function set_height( $args, $upscale = false ) {
 			if ( substr( $args, -1 ) == '%' )
 				$this->new_height = round( $this->int_h * abs( intval( $args ) ) / 100 );
 			else
@@ -572,7 +593,7 @@ if ( ! class_exists( 'Gif_Image' ) ) {
 				return;
 			}
 			// sane limit when upscaling, defaults to 1000
-			if ( $this->new_height > $this->int_h && $upscale && $this->new_height > PHOTON__UPSCALE_MAX_PIXELS ) {
+			if ( $this->new_height > $this->int_h && $upscale && $this->new_height > $this->upscale_max_pixels ) {
 				// if the sizes are too big, then we serve the original size
 				$this->new_width  = $this->int_w;
 				$this->new_height = $this->int_h;
@@ -587,7 +608,7 @@ if ( ! class_exists( 'Gif_Image' ) ) {
 			$this->crop_height = $this->new_height;
 		}
 
-		private function setwidth( $args, $upscale = false ) {
+		private function set_width( $args, $upscale = false ) {
 			if ( '%' == substr( $args, -1 ) )
 				$this->new_width = round( $this->int_w * abs( intval( $args ) ) / 100 );
 			else
@@ -602,7 +623,7 @@ if ( ! class_exists( 'Gif_Image' ) ) {
 			}
 
 			// Sane limit when upscaling, defaults to 1000
-			if ( $this->new_width > $this->int_w && $upscale && $this->new_width > PHOTON__UPSCALE_MAX_PIXELS ) {
+			if ( $this->new_width > $this->int_w && $upscale && $this->new_width > $this->upscale_max_pixels ) {
 				// if the sizes are too big, then we serve the original size
 				$this->new_width  = $this->int_w;
 				$this->new_height = $this->int_h;
@@ -640,8 +661,8 @@ if ( ! class_exists( 'Gif_Image' ) ) {
 
 				// check that if we have made it bigger than the images original size, that we remain with bounds
 				if ( $this->new_width >= $this->int_w && $this->new_height >= $this->int_h ) {
-					if ( ( $this->new_width > PHOTON__UPSCALE_MAX_PIXELS ) ||
-						( $this->new_height > PHOTON__UPSCALE_MAX_PIXELS ) ) {
+					if ( ( $this->new_width > $this->upscale_max_pixels ) ||
+						( $this->new_height > $this->upscale_max_pixels ) ) {
 						$this->new_width  = $this->int_w;
 						$this->new_height = $this->int_h;
 					}
@@ -656,7 +677,6 @@ if ( ! class_exists( 'Gif_Image' ) ) {
 				$this->new_height = $this->int_h;
 				return;
 			}
-
 			list( $end_w, $end_h ) = explode( ',', $args );
 
 			$end_w = abs( intval( $end_w ) );
@@ -672,21 +692,22 @@ if ( ! class_exists( 'Gif_Image' ) ) {
 
 				if ( $original_aspect >= $new_aspect ) {
 					$this->new_height = $end_h;
-					$this->new_width = $this->int_w / ( $this->int_h / $end_h );
+					$this->new_width = round( $this->int_w / ( $this->int_h / $end_h ) );
 					// check we haven't overstepped the width
 					if ( $this->new_width > $end_w ) {
-						$this->new_height = $this->new_height - round( ( $this->new_width - $end_w ) * $new_aspect );
 						$this->new_width = $end_w;
+						$this->new_height = round( $this->int_h / ( $this->int_w / $end_w ) );
 					}
 				} else {
 					$this->new_width = $end_w;
-					$this->new_height = $this->int_h / ( $this->int_w / $end_w );
+					$this->new_height = round( $this->int_h / ( $this->int_w / $end_w ) );
 					// check we haven't overstepped the height
 					if ( $this->new_height > $end_h ) {
-						$this->new_width = $this->new_width - round( ( $this->new_height - $end_h ) * $new_aspect );
 						$this->new_height = $end_h;
+						$this->new_width = round( $this->int_w / ( $this->int_h / $end_h ) );
 					}
 				}
+				$this->fit = true;
 			}
 		}
 
@@ -697,7 +718,6 @@ if ( ! class_exists( 'Gif_Image' ) ) {
 				$this->new_height = $this->int_h;
 				return;
 			}
-
 			$args = explode( ',', $args );
 
 			// if we don't have the correct number of args, default
@@ -732,6 +752,32 @@ if ( ! class_exists( 'Gif_Image' ) ) {
 			$this->crop = true;
 		}
 
+		private function crop_offset( $args ) {
+			// if the args are malformed, default to the original size
+			if ( false === strpos( $args, ',' ) ) {
+				$this->new_width  = $this->int_w;
+				$this->new_height = $this->int_h;
+				return;
+			}
+			$args = explode( ',', $args );
+
+			// if we don't have the correct number of args, default
+			if ( count( $args ) != 4 ) {
+				$this->new_width  = $this->int_w;
+				$this->new_height = $this->int_h;
+				return;
+			}
+
+			$this->crop_width = max( 0, min( $this->int_w, intval( $args[2] ) ) );
+			$this->crop_height = max( 0, min( $this->int_h, intval( $args[3] ) ) );
+			$this->s_x = intval( $args[0] );
+			$this->s_y = intval( $args[1] );
+
+			$this->new_width = $this->crop_width;
+			$this->new_height = $this->crop_height;
+			$this->crop = true;
+		}
+
 		private function resize_and_crop( $args ) {
 			// if the args are malformed, default to the original size
 			if ( false === strpos( $args, ',' ) ) {
@@ -756,39 +802,77 @@ if ( ! class_exists( 'Gif_Image' ) ) {
 
 			// If the original and new images are proportional (no cropping needed), just do a standard resize
 			if ( $ratio_orig == $ratio_end ) {
-				$this->setwidth( $end_w, true );
+				$this->set_width( $end_w, true );
 			} else {
-					$aspect_ratio = $this->int_w / $this->int_h;
-
-					if ( $end_w >= $this->int_w && $end_h >= $this->int_h ) {
-						$this->new_width = max( $end_w, $this->int_w );
-						$this->new_height = max( $end_h, $this->int_h );
-						if ( ( $this->new_width > PHOTON__UPSCALE_MAX_PIXELS ) ||
-							( $this->new_height > PHOTON__UPSCALE_MAX_PIXELS ) ) {
-							$this->new_width  = $this->int_w;
-							$this->new_height = $this->int_h;
-						}
-					} else {
-						$this->new_width = $end_w;
-						$this->new_height = $end_h;
-					}
+				if ( $end_w >= $this->int_w && $end_h >= $this->int_h ) {
+					$this->new_width = max( $end_w, $this->int_w );
+					$this->new_height = max( $end_h, $this->int_h );
+				} else {
+					$this->new_width = $end_w;
+					$this->new_height = $end_h;
+				}
+
+				if ( ! $this->new_width )
+					$this->new_width = intval( $this->new_height * $ratio_orig );
+				if ( ! $this->new_height )
+					$this->new_height = intval( $this->new_width / $ratio_orig );
+
+				// Check if the width or height are too large, if they are then default to original size
+				if ( ( ( $this->new_width > $this->int_w ) && ( $this->new_width > $this->upscale_max_pixels ) ) ||
+					( $this->new_height > $this->int_h && ( $this->new_height > $this->upscale_max_pixels ) ) ) {
+					$this->new_width = $this->int_w;
+					$this->new_height = $this->int_h;
+					return;
+				}
+
+				$size_ratio = max( $this->new_width / $this->int_w, $this->new_height / $this->int_h );
+				$this->crop_width = min( ceil( $this->new_width / $size_ratio ), $this->int_w );
+				$this->crop_height = min( ceil( $this->new_height / $size_ratio ), $this->int_h );
+
+				$this->s_x = round( ( $this->int_w - $this->crop_width ) / 2 );
+				$this->s_y = round( ( $this->int_h - $this->crop_height ) / 2 );
+				$this->crop = true;
+			}
+		}
 
-					if ( ! $this->new_width )
-						$this->new_width = intval( $this->new_height * $aspect_ratio );
-					if ( ! $this->new_height )
-						$this->new_height = intval( $this->new_width / $aspect_ratio );
+		public function process_image( $new_w, $new_h, $crop, $s_x, $s_y, $crop_w, $crop_h ) {
+			// if the gif image has an invalid size for either value, do not process it
+			if ( 1 > $this->int_w || 1 > $this->int_h )
+				return false;
 
-					$size_ratio = max( $this->new_width / $this->int_w, $this->new_height / $this->int_h );
-					$this->crop_width = min( ceil( $this->new_width / $size_ratio ), $this->int_w );
-					$this->crop_height = min( ceil( $this->new_height / $size_ratio ), $this->int_h );
+			$this->new_width   = $new_w;
+			$this->new_height  = $new_h;
+			$this->crop        = $crop;
+			$this->s_x         = $s_x;
+			$this->s_y         = $s_y;
+			$this->crop_width  = $crop_w;
+			$this->crop_height = $crop_h;
+
+			// we fail if the image size is too small
+			if ( 1 > $this->new_width || 1 > $this->new_height )
+				return false;
+
+			if ( $this->crop ) {
+				$this->resize_ratios[0] = $this->new_width / $this->crop_width;
+				$this->resize_ratios[1] = $this->new_height / $this->crop_height;
+			} else {
+				$this->resize_ratios[0] = $this->new_width / $this->int_w;
+				$this->resize_ratios[1] = $this->new_height / $this->int_h;
+			}
 
-					$this->s_x = round( ( $this->int_w - $this->crop_width ) / 2 );
-					$this->s_y = round( ( $this->int_h - $this->crop_height ) / 2 );
-					$this->crop = true;
+			$this->image_data = '';
+			for ( $i = 0; $i < $this->frame_count; $i++ ) {
+				$this->image_data .= $this->repack_frame( $this->process_frame( $this->get_frame_image( $i ), $i ), $i );
+				$this->frame_array[ $i ] = null;
 			}
+
+			return true;
 		}
 
-		public function process_image() {
+		public function process_image_functions( $upscale_max_pixels ) {
+			if ( isset( $upscale_max_pixels ) )
+				$this->upscale_max_pixels = $upscale_max_pixels;
+
 			// if the gif image has an invalid size for either value, do not process it
 			if ( 1 > $this->int_w || 1 > $this->int_h )
 				return false;
@@ -796,7 +880,7 @@ if ( ! class_exists( 'Gif_Image' ) ) {
 			// we need at least one action to perform otherwise we should just send the original
 			if ( 0 == count( $this->pre_process_actions ) ) {
 				$this->pre_process_actions[] = Array (
-												'func_name' => 'setWidth',
+												'func_name' => 'set_width',
 												'params'    => $this->int_w,
 												);
 			}
@@ -805,15 +889,13 @@ if ( ! class_exists( 'Gif_Image' ) ) {
 			// do the pre-processing functions
 			foreach ( $this->pre_process_actions as $action ) {
 				$this->$action[ 'func_name' ]( $action[ 'params' ] );
-				if ( 'crop' == $action[ 'func_name' ] )
+				if ( 'crop' == $action[ 'func_name' ] || 'crop_offset' == $action[ 'func_name' ] )
 					$cropped = true;
 			}
 
-			// zoom functionality is not supported with the 'crop' function
-			if ( ! $cropped ) {
-				// check if zoom needs to be run, and run if neccessary
-				if ( isset( $_GET['zoom'] ) )
-					$this->zoom( $_GET['zoom'] );
+			// zoom functionality is not supported with the 'crop-style' functions
+			if ( ! $cropped && isset( $_GET['zoom'] ) && $this->zoom_enabled ) {
+				$this->zoom( $_GET['zoom'] );
 			}
 
 			// we fail if the image size is too small
@@ -829,13 +911,15 @@ if ( ! class_exists( 'Gif_Image' ) ) {
 			}
 
 			$this->image_data = '';
-			for ( $i = 0; $i < $this->frame_count; $i++ )
+			for ( $i = 0; $i < $this->frame_count; $i++ ) {
 				$this->image_data .= $this->repack_frame( $this->process_frame( $this->get_frame_image( $i ), $i ), $i );
+				$this->frame_array[ $i ] = null;
+			}
 
 			return true;
 		}
 
-		public function get_imageblob() {
+		public function get_image_blob() {
 			$gm = $this->gif_header;
 			$gm[10] = $gm[10] & 0x7F;
 			$i_bytes = $this->int_raw( round( ( $this->crop ? $this->crop_width : $this->int_w ) * $this->resize_ratios[0] ) ? : 1 );
@@ -845,6 +929,8 @@ if ( ! class_exists( 'Gif_Image' ) ) {
 			$gm[8] = $i_bytes[0];
 			$gm[9] = $i_bytes[1];
 
+			$this->image_data = $gm . $this->g_mod . $this->image_data;
+
 			$con = '';
 			if ( strlen( $this->g_mode ) )
 				$con = $this->g_mode . "\x3B";
@@ -854,7 +940,14 @@ if ( ! class_exists( 'Gif_Image' ) ) {
 			if ( ! $this->au )
 				$con = "\x21\xFE\x0Eautomattic_inc\x00" . $con;
 
-			return $gm . $this->g_mod . $this->image_data . ( iconv_strlen( $con ) >= 19 ? $con : "\x21" );
+			$this->image_data .= ( strlen( $con ) >= 19 ? $con : "\x21" );
+
+			if ( ! headers_sent() ) {
+				header( 'ETag: "' . substr( md5( strlen( $this->image_data ) . '.' . time() ), 0, 16 ) . '"' );
+				header( 'Content-Length: ' . strlen( $this->image_data ) );
+			}
+
+			return $this->image_data;
 		}
 
 		public function add_function( $function_name, $arguments ) {
@@ -870,5 +963,21 @@ if ( ! class_exists( 'Gif_Image' ) ) {
 												);
 			}
 		}
+
+		public function get_frame_count() {
+			return $this->frame_count;
+		}
+
+		public function get_image_width() {
+			return intval( $this->int_w );
+		}
+
+		public function get_image_height() {
+			return intval( $this->int_h );
+		}
+
+		public function disable_zoom() {
+			$this->zoom_enabled = false;
+		}
 	}
 }
diff --git a/libjpeg.php b/libjpeg.php
index 9eb68267feda819922c89adebe1a8c309c252bfa..64fbbf5fff3e0f7f5810007172999fb8977edd76 100644
--- a/libjpeg.php
+++ b/libjpeg.php
@@ -1,6 +1,6 @@
 <?php
 
-function get_jpeg_header_data( &$buff, $buff_len, $want=null ) { 
+function get_jpeg_header_data( &$buff, $buff_len, $want=null ) {
 	$data = buffer_read( $buff, $buff_len, 2, true ); // Read the first two characters
 	// Check that the first two characters are 0xFF 0xDA  (SOI - Start of image)
 	if ( $data != "\xFF\xD8" ) {
@@ -13,13 +13,13 @@ function get_jpeg_header_data( &$buff, $buff_len, $want=null ) {
 		// NO FF found - close file and return - JPEG is probably corrupted
 		return false;
 	}
-	// Cycle through the file until, one of: 
+	// Cycle through the file until, one of:
 	//   1) an EOI (End of image) marker is hit,
 	//   2) we have hit the compressed image data (no more headers are allowed after data)
 	//   3) or end of file is hit
 	$headerdata = array();
 	$hit_compressed_image_data = FALSE;
-	while ( ( $data{1} != "\xD9" ) && ( !$hit_compressed_image_data) && ( $data != '' ) ) { 
+	while ( ( $data{1} != "\xD9" ) && ( !$hit_compressed_image_data) && ( $data != '' ) ) {
 		// Found a segment to look at.
 		// Check that the segment marker is not a Restart marker - restart markers don't have size or data after them
 		if (  ( ord($data{1}) < 0xD0 ) || ( ord($data{1}) > 0xD7 ) ) {
@@ -32,11 +32,11 @@ function get_jpeg_header_data( &$buff, $buff_len, $want=null ) {
 			$segdata = buffer_read( $buff, $buff_len, $decodedsize['size'] - 2 );
 			// Store the segment information in the output array
 			if ( !$want || $want == ord($data{1}) ) {
-				$headerdata[] = (object)array(  
+				$headerdata[] = (object)array(
 					"SegType" => ord($data{1}),
 					"SegName" => $GLOBALS[ "JPEG_Segment_Names" ][ ord($data{1}) ],
 					"SegDesc" => $GLOBALS[ "JPEG_Segment_Descriptions" ][ ord($data{1}) ],
-					"SegData" => $segdata 
+					"SegData" => $segdata
 				);
 			}
 		}
@@ -172,7 +172,7 @@ $GLOBALS[ "JPEG_Segment_Descriptions" ] = array(
  * and how it handles identify -verbose $filename to present you with a Quality
  * number.  This number should be considered approximate.  It's essentially
  * based upon the numbers used to perform compression on the original image
- * data... 
+ * data...
  *
  * See: http://www.obrador.com/essentialjpeg/headerinfo.htm
  * See: http://www.impulseadventure.com/photo/jpeg-quantization.html
@@ -192,7 +192,7 @@ function get_jpeg_quality( &$buff, $buff_len = null ) {
 				  143,  139,  132,  128,  125,  119,  115,  108,  104,   99,
 				   94,   90,   84,   79,   74,   70,   64,   59,   55,   49,
 				   45,   40,   34,   30,   25,   20,   15,   11,    6,    4,
-					0	
+					0
 			), // hash
 			'sums' => array (
 				 32640, 32635, 32266, 31495, 30665, 29804, 29146, 28599, 28104,
@@ -239,18 +239,18 @@ function get_jpeg_quality( &$buff, $buff_len = null ) {
 			), // sums
 		), // single
 	); // tables
-	
+
 	if ( ! isset( $buff_len ) )
 		$buff_len = strlen( $buff );
 
 	$headers = get_jpeg_header_data( $buff, $buff_len, 0xDB );
 	if ( !is_array( $headers ) || !count( $headers ) )
 		return 100;
-	$header = $headers[0];	
+	$header = $headers[0];
 	$quality = 0;
 	if ( strlen($header->SegData) > 128 ) {
 		$entry = array( 0 => array(), 1 => array() );
-		foreach ( str_split( substr( $header->SegData, 1, 64) ) as $chr ) 
+		foreach ( str_split( substr( $header->SegData, 1, 64) ) as $chr )
 			$entry[0][] = ord($chr);
 		foreach ( str_split( substr( $header->SegData, -64) ) as $chr )
 			$entry[1][] = ord($chr);
@@ -270,8 +270,60 @@ function get_jpeg_quality( &$buff, $buff_len = null ) {
 	for( $i = 0; $i <= 100; $i++ ) {
 		if ( ( $qvalue < $tables[$table]['hash'][$i] ) && ( $sum < $tables[$table]['sums'][$i] ) )
 			continue;
-		return $i;
+		return min( $i+1, 100 );
 	}
 	return 100; // go with a safe value
 }
 
+function exifrotate( $file, $image, $strip ) {
+	if ( ! function_exists( 'exif_read_data' ) )
+		return;
+
+	if ( ! in_array( $strip, array( 'all', 'info' ) ) )
+		return;
+
+	$exif = @exif_read_data( $file );
+
+	if ( ! isset( $exif[ 'Orientation' ] ) )
+		return;
+
+	$degrees = 0;
+	switch( $exif[ 'Orientation' ] ) {
+		case 3:
+			$degrees = 180;
+			break;
+		case 6:
+			$degrees = 90;
+			break;
+		case 8:
+			$degrees = 270;
+			break;
+	}
+
+	if ( $degrees ) {
+		$image->rotateImage( 'black', $degrees );
+		// We need to write again, since we wrote it earlier to read EXIF data
+		$image->write( $file );
+	}
+}
+
+function jpegoptim( $file, $strip = false ) {
+	if ( false === JPEGOPTIM )
+		return;
+
+	$cmd = JPEGOPTIM . ' -T0.0 --all-progressive';
+	switch ( $strip ) {
+		case 'all':
+			$cmd .= ' -f --strip-all';
+			break;
+		case 'info':
+			$cmd .= ' -f --strip-com --strip-exif --strip-iptc';
+			break;
+		case 'color':
+			$cmd .= ' -f --strip-icc';
+			break;
+	}
+	$cmd .= " -p $file";
+	exec( $cmd );
+}
+