<?php // WEBPSHIM 22-07 version /*\ add these rewrite lines to your .htaccess file: ##WEBPSHIM-BEGIN - DO NOT EDIT! RewriteCond %{REQUEST_FILENAME} (?i).+\.(jpe?g|png)$ RewriteCond %{HTTP_ACCEPT} image/webp RewriteCond "%{DOCUMENT_ROOT}/webpshim_cache/%{REQUEST_URI}.webp" -f RewriteCond %{QUERY_STRING} !^nowebp RewriteRule (?i).+\.(jpe?g|png)$ /webpshim_cache/%{REQUEST_URI}.webp [T=image/webp,E=accept:1,L] RewriteCond %{REQUEST_FILENAME} (?i).+\.(jpe?g|png)$ RewriteCond %{HTTP_ACCEPT} image/webp RewriteCond "%{DOCUMENT_ROOT}/webpshim_cache/%{REQUEST_URI}.webp" !-f RewriteCond %{REQUEST_URI} !admin RewriteCond %{QUERY_STRING} !^nowebp RewriteRule (?i).+\.(jpe?g|png)$ /webpshim.php [T=image/webp,E=accept:1,L] #RewriteRule (?i).+\.(jpe?g|png)$ /webpshim.php [T=image/webp,H=application/x-httpd-alt-php74,E=accept:1,L] <IfModule mod_headers.c> Header append Vary Accept env=REDIRECT_accept </IfModule> AddType image/webp .webp ##WEBPSHIM-END optional: add a cron job to clear the webp cache regularly \*/ function mb_stringbeforelast($haystack, $needle='.') { if (($pos = mb_strrpos($haystack, $needle)) === false) { return false; } return mb_substr($haystack, 0, $pos); } function mb_stringafterlast($haystack, $needle='.') { if (($pos = mb_strrpos($haystack, $needle)) === false) { return false; } return mb_substr($haystack, $pos + 1); } $debug = false; function debuglog($s) { global $debug; if (!$debug) return; file_put_contents(__DIR__ . '/webpshim.debug.log', date('Y-m-d H:i:s') . ' - ' . print_r($s, true) . "\n", FILE_APPEND); } $DIR_ROOT = str_replace('//' ,'/', rtrim($_SERVER['DOCUMENT_ROOT'], '/').'/'); // TODO put these in an ini file $WEBP_QUALITY = 75; $logging = true; //////////////////////////////////////////////////////////////////////////////// //// get and validate file path $source_path = rawurldecode($_SERVER['REQUEST_URI']); // remove query string if (strpos($source_path,'?') !== false) { $source_path = mb_stringbeforelast($source_path,'?'); } // remove leading slash if (mb_substr($source_path, 0, 1) === '/') { $source_path = mb_substr($source_path, 1); } $source_fullpath = $DIR_ROOT . $source_path; if (!file_exists($source_fullpath)) { http_response_code(404); exit; } //// get and validate file extension $source_ext = mb_strtolower(mb_stringafterlast($source_path, '.')); if (!in_array($source_ext, ['jpg', 'jpeg', 'png'])) { // not a supported format, read file contents directly and exit header('Accept-Ranges: bytes'); header('Content-Length: ' . filesize($source_fullpath)); $mime = mime_content_type($source_fullpath); if ($mime) { header('Content-Type: ' . $mime); } header_remove('Server'); header_remove('x-powered-by'); if (function_exists('apache_setenv')) { apache_setenv('no-gzip', 1); } else { header('Content-Encoding: none'); header_remove('Content-Encoding'); } readfile($source_fullpath); exit; } //// get source image details, set up initial dest image details $source_size = filesize($source_fullpath); $source_mime = getimagesize($source_fullpath)['mime']; $dest_path = $source_path . '.webp'; $dest_fullpath = $DIR_ROOT . 'webpshim_cache/' . $dest_path; $dest_size = 0; // filesize of destination webp (once generated) $created = false; // whether the webp was created if(mb_strpos($_SERVER['HTTP_ACCEPT'], 'image/webp') !== false) { // client supports WebP if (!is_dir(dirname($dest_fullpath))) { mkdir(dirname($dest_fullpath), 0755, true); } //// try using imagemagick library first, fallback to gd library if ( // try using imagemagick library first extension_loaded('imagick') && Imagick::queryFormats('WEBP') && $image = new Imagick($source_fullpath) ) { $image->stripImage(); $image->setImageFormat('webp'); $image->setImageCompressionQuality($WEBP_QUALITY); // HARDCODED (for now) // TODO: support for lossless WebP when converting from png if ($source_ext == 'png') { $image->setImageAlphaChannel(imagick::ALPHACHANNEL_ACTIVATE); $image->setBackgroundColor(new ImagickPixel('transparent')); } if ($image->writeImage($dest_fullpath)) { $created = true; $dest_size = filesize($dest_fullpath); } $image->clear(); } elseif ( // fallback to gd library gd_info()['WebP Support'] && function_exists('imagewebp') && function_exists($imagecreate_func = "imagecreatefrom".strtolower(substr($source_mime, strpos($source_mime, '/') + 1))) && ($image = $imagecreate_func($source_fullpath)) !== false ) { if ($source_ext === 'png') { imagepalettetotruecolor($image); //imagealphablending($image, true); //imagesavealpha($image, true); } if (imagewebp($image, $dest_fullpath, $WEBP_QUALITY)) { $created = true; $dest_size = filesize($dest_fullpath); } imagedestroy($image); } if ($dest_size) { // webp successfully created $dest_mime = getimagesize($dest_fullpath)['mime']; } elseif ($created && file_exists($dest_fullpath)) { // reported webp was created but filesize is 0 - likely webp creation failed, remove empty webp and return original file unlink($dest_fullpath); $created = false; } } if ($dest_size) { $return_size = $dest_size; $return_mime = $dest_mime; $return_fullpath = $dest_fullpath; } else { // either webp failed to generate or client does not support webp - return original image $return_size = $source_size; $return_mime = $source_mime; $return_fullpath = $source_fullpath; } //// now return the image if (ob_get_level()) { ob_end_clean(); // just in case... } http_response_code(200); header('Accept-Ranges: bytes'); header('Content-Length: ' . $return_size); header('Content-Type: ' . $return_mime); header_remove('Server'); header_remove('x-powered-by'); if ($created) { header('X-Gen: webpshim'); } if (function_exists('apache_setenv')) { apache_setenv('no-gzip', 1); } else { header('Content-Encoding: none'); header_remove('Content-Encoding'); } readfile($return_fullpath); exit;