图像功能,允许在PHP中裁剪和调整图像大小,而无需使用太多内存


Image function that allows images to be cropped and resized in PHP without using so much memory

我已经在我构建的一些站点中使用了一段时间,这对于画廊非常有用,但它占用了大量内存,并且我在错误日志中不断收到内存分配错误。 有什么可以更改以使此脚本更好的吗?

<?php
// Smart Image Resizer 1.4.1
// Resizes images, intelligently sharpens, crops based on width:height ratios, color fills
// transparent GIFs and PNGs, and caches variations for optimal performance
// Created by: Joe Lencioni (http://shiftingpixel.com)
// Date: August 6, 2008
// Based on: http://veryraw.com/history/2005/03/image-resizing-with-php/
/////////////////////
// LICENSE
/////////////////////
// I love to hear when my work is being used, so if you decide to use this, feel encouraged
// to send me an email. Smart Image Resizer is released under a Creative Commons
// Attribution-Share Alike 3.0 United States license
// (http://creativecommons.org/licenses/by-sa/3.0/us/). All I ask is that you include a link
// back to Shifting Pixel (either this page or shiftingpixel.com), but don’t worry about
// including a big link on each page if you don’t want to–one will do just nicely. Feel
// free to contact me to discuss any specifics (joe@shiftingpixel.com).
/////////////////////
// REQUIREMENTS
/////////////////////
// PHP and GD
/////////////////////
// PARAMETERS
/////////////////////
// Parameters need to be passed in through the URL's query string:
// image        absolute path of local image starting with "/" (e.g. /images/toast.jpg)
// width        maximum width of final image in pixels (e.g. 700)
// height       maximum height of final image in pixels (e.g. 700)
// color        (optional) background hex color for filling transparent PNGs (e.g. 900 or 16a942)
// cropratio    (optional) ratio of width to height to crop final image (e.g. 1:1 or 3:2)
// nocache      (optional) does not read image from the cache
// quality      (optional, 0-100, default: 90) quality of output image
/////////////////////
// EXAMPLES
/////////////////////
// Resizing a JPEG:
// <img src="/image.php/image-name.jpg?width=100&amp;height=100&amp;image=/path/to/image.jpg" alt="Don't forget your alt text" />
// Resizing and cropping a JPEG into a square:
// <img src="/image.php/image-name.jpg?width=100&amp;height=100&amp;cropratio=1:1&amp;image=/path/to/image.jpg" alt="Don't forget your alt text" />
// Matting a PNG with #990000:
// <img src="/image.php/image-name.png?color=900&amp;image=/path/to/image.png" alt="Don't forget your alt text" />
/////////////////////
// CODE STARTS HERE
/////////////////////
if (!isset($_GET['image']))
{
    header('HTTP/1.1 400 Bad Request');
    echo 'Error: no image was specified';
    exit();
}
define('MEMORY_TO_ALLOCATE',    '100M');
define('DEFAULT_QUALITY',       90);
define('CURRENT_DIR',           dirname(__FILE__));
define('CACHE_DIR_NAME',        '/imagecache/');
define('CACHE_DIR',             CURRENT_DIR . CACHE_DIR_NAME);
define('DOCUMENT_ROOT',         $_SERVER['DOCUMENT_ROOT']);
// Images must be local files, so for convenience we strip the domain if it's there
$image          = preg_replace('/^(s?f|ht)tps?:'/'/[^'/]+/i', '', (string) $_GET['image']);
// For security, directories cannot contain ':', images cannot contain '..' or '<', and
// images must start with '/'
if ($image{0} != '/' || strpos(dirname($image), ':') || preg_match('/('.'.|<|>)/', $image))
{
    header('HTTP/1.1 400 Bad Request');
    echo 'Error: malformed image path. Image paths must begin with ''/''';
    exit();
}
// If the image doesn't exist, or we haven't been told what it is, there's nothing
// that we can do
if (!$image)
{
    header('HTTP/1.1 400 Bad Request');
    echo 'Error: no image was specified';
    exit();
}
// Strip the possible trailing slash off the document root
$docRoot    = preg_replace('/'/$/', '', DOCUMENT_ROOT);
if (!file_exists($docRoot . $image))
{
    header('HTTP/1.1 404 Not Found');
    echo 'Error: image does not exist: ' . $docRoot . $image;
    exit();
}
// Get the size and MIME type of the requested image
$size   = GetImageSize($docRoot . $image);
$mime   = $size['mime'];
// Make sure that the requested file is actually an image
if (substr($mime, 0, 6) != 'image/')
{
    header('HTTP/1.1 400 Bad Request');
    echo 'Error: requested file is not an accepted type: ' . $docRoot . $image;
    exit();
}
$width          = $size[0];
$height         = $size[1];
$maxWidth       = (isset($_GET['width'])) ? (int) $_GET['width'] : 0;
$maxHeight      = (isset($_GET['height'])) ? (int) $_GET['height'] : 0;
if (isset($_GET['color']))
    $color      = preg_replace('/[^0-9a-fA-F]/', '', (string) $_GET['color']);
else
    $color      = FALSE;
// If either a max width or max height are not specified, we default to something
// large so the unspecified dimension isn't a constraint on our resized image.
// If neither are specified but the color is, we aren't going to be resizing at
// all, just coloring.
if (!$maxWidth && $maxHeight)
{
    $maxWidth   = 99999999999999;
}
elseif ($maxWidth && !$maxHeight)
{
    $maxHeight  = 99999999999999;
}
elseif ($color && !$maxWidth && !$maxHeight)
{
    $maxWidth   = $width;
    $maxHeight  = $height;
}
// If we don't have a max width or max height, OR the image is smaller than both
// we do not want to resize it, so we simply output the original image and exit
if ((!$maxWidth && !$maxHeight) || (!$color && $maxWidth >= $width && $maxHeight >= $height))
{
    $data   = file_get_contents($docRoot . '/' . $image);
    $lastModifiedString = gmdate('D, d M Y H:i:s', filemtime($docRoot . '/' . $image)) . ' GMT';
    $etag               = md5($data);
    doConditionalGet($etag, $lastModifiedString);
    header("Content-type: $mime");
    header('Content-Length: ' . strlen($data));
    echo $data;
    exit();
}
// Ratio cropping
$offsetX    = 0;
$offsetY    = 0;
if (isset($_GET['cropratio']))
{
    $cropRatio      = explode(':', (string) $_GET['cropratio']);
    if (count($cropRatio) == 2)
    {
        $ratioComputed      = $width / $height;
        $cropRatioComputed  = (float) $cropRatio[0] / (float) $cropRatio[1];
        if ($ratioComputed < $cropRatioComputed)
        { // Image is too tall so we will crop the top and bottom
            $origHeight = $height;
            $height     = $width / $cropRatioComputed;
            $offsetY    = ($origHeight - $height) / 2;
        }
        else if ($ratioComputed > $cropRatioComputed)
        { // Image is too wide so we will crop off the left and right sides
            $origWidth  = $width;
            $width      = $height * $cropRatioComputed;
            $offsetX    = ($origWidth - $width) / 2;
        }
    }
}
// Setting up the ratios needed for resizing. We will compare these below to determine how to
// resize the image (based on height or based on width)
$xRatio     = $maxWidth / $width;
$yRatio     = $maxHeight / $height;
if ($xRatio * $height < $maxHeight)
{ // Resize the image based on width
    $tnHeight   = ceil($xRatio * $height);
    $tnWidth    = $maxWidth;
}
else // Resize the image based on height
{
    $tnWidth    = ceil($yRatio * $width);
    $tnHeight   = $maxHeight;
}
// Determine the quality of the output image
$quality    = (isset($_GET['quality'])) ? (int) $_GET['quality'] : DEFAULT_QUALITY;
// Before we actually do any crazy resizing of the image, we want to make sure that we
// haven't already done this one at these dimensions. To the cache!
// Note, cache must be world-readable
// We store our cached image filenames as a hash of the dimensions and the original filename
$resizedImageSource     = $tnWidth . 'x' . $tnHeight . 'x' . $quality;
if ($color)
    $resizedImageSource .= 'x' . $color;
if (isset($_GET['cropratio']))
    $resizedImageSource .= 'x' . (string) $_GET['cropratio'];
$resizedImageSource     .= '-' . $image;
$resizedImage   = md5($resizedImageSource);
$resized        = CACHE_DIR . $resizedImage;
// Check the modified times of the cached file and the original file.
// If the original file is older than the cached file, then we simply serve up the cached file
if (!isset($_GET['nocache']) && file_exists($resized))
{
    $imageModified  = filemtime($docRoot . $image);
    $thumbModified  = filemtime($resized);
    if($imageModified < $thumbModified) {
        $data   = file_get_contents($resized);
        $lastModifiedString = gmdate('D, d M Y H:i:s', $thumbModified) . ' GMT';
        $etag               = md5($data);
        doConditionalGet($etag, $lastModifiedString);
        header("Content-type: $mime");
        header('Content-Length: ' . strlen($data));
        echo $data;
        exit();
    }
}
// We don't want to run out of memory
ini_set('memory_limit', MEMORY_TO_ALLOCATE);
// Set up a blank canvas for our resized image (destination)
$dst    = imagecreatetruecolor($tnWidth, $tnHeight);
// Set up the appropriate image handling functions based on the original image's mime type
switch ($size['mime'])
{
    case 'image/gif':
        // We will be converting GIFs to PNGs to avoid transparency issues when resizing GIFs
        // This is maybe not the ideal solution, but IE6 can suck it
        $creationFunction   = 'ImageCreateFromGif';
        $outputFunction     = 'ImagePng';
        $mime               = 'image/png'; // We need to convert GIFs to PNGs
        $doSharpen          = FALSE;
        $quality            = round(10 - ($quality / 10)); // We are converting the GIF to a PNG and PNG needs a compression level of 0 (no compression) through 9
    break;
    case 'image/x-png':
    case 'image/png':
        $creationFunction   = 'ImageCreateFromPng';
        $outputFunction     = 'ImagePng';
        $doSharpen          = FALSE;
        $quality            = round(10 - ($quality / 10)); // PNG needs a compression level of 0 (no compression) through 9
    break;
    default:
        $creationFunction   = 'ImageCreateFromJpeg';
        $outputFunction     = 'ImageJpeg';
        $doSharpen          = TRUE;
    break;
}
// Read in the original image
$src    = $creationFunction($docRoot . $image);
if (in_array($size['mime'], array('image/gif', 'image/png')))
{
    if (!$color)
    {
        // If this is a GIF or a PNG, we need to set up transparency
        imagealphablending($dst, false);
        imagesavealpha($dst, true);
    }
    else
    {
        // Fill the background with the specified color for matting purposes
        if ($color[0] == '#')
            $color = substr($color, 1);
        $background = FALSE;
        if (strlen($color) == 6)
            $background = imagecolorallocate($dst, hexdec($color[0].$color[1]), hexdec($color[2].$color[3]), hexdec($color[4].$color[5]));
        else if (strlen($color) == 3)
            $background = imagecolorallocate($dst, hexdec($color[0].$color[0]), hexdec($color[1].$color[1]), hexdec($color[2].$color[2]));
        if ($background)
            imagefill($dst, 0, 0, $background);
    }
}
// Resample the original image into the resized canvas we set up earlier
ImageCopyResampled($dst, $src, 0, 0, $offsetX, $offsetY, $tnWidth, $tnHeight, $width, $height);
if ($doSharpen)
{
    // Sharpen the image based on two things:
    //  (1) the difference between the original size and the final size
    //  (2) the final size
    $sharpness  = findSharp($width, $tnWidth);
    $sharpenMatrix  = array(
        array(-1, -2, -1),
        array(-2, $sharpness + 12, -2),
        array(-1, -2, -1)
    );
    $divisor        = $sharpness;
    $offset         = 0;
    imageconvolution($dst, $sharpenMatrix, $divisor, $offset);
}
// Make sure the cache exists. If it doesn't, then create it
if (!file_exists(CACHE_DIR))
    mkdir(CACHE_DIR, 0755);
// Make sure we can read and write the cache directory
if (!is_readable(CACHE_DIR))
{
    header('HTTP/1.1 500 Internal Server Error');
    echo 'Error: the cache directory is not readable';
    exit();
}
else if (!is_writable(CACHE_DIR))
{
    header('HTTP/1.1 500 Internal Server Error');
    echo 'Error: the cache directory is not writable';
    exit();
}
// Write the resized image to the cache
$outputFunction($dst, $resized, $quality);
// Put the data of the resized image into a variable
ob_start();
$outputFunction($dst, null, $quality);
$data   = ob_get_contents();
ob_end_clean();
// Clean up the memory
ImageDestroy($src);
ImageDestroy($dst);
// See if the browser already has the image
$lastModifiedString = gmdate('D, d M Y H:i:s', filemtime($resized)) . ' GMT';
$etag               = md5($data);
doConditionalGet($etag, $lastModifiedString);
// Send the image to the browser with some delicious headers
header("Content-type: $mime");
header('Content-Length: ' . strlen($data));
echo $data;
function findSharp($orig, $final) // function from Ryan Rud (http://adryrun.com)
{
    $final  = $final * (750.0 / $orig);
    $a      = 52;
    $b      = -0.27810650887573124;
    $c      = .00047337278106508946;
    $result = $a + $b * $final + $c * $final * $final;
    return max(round($result), 0);
} // findSharp()
function doConditionalGet($etag, $lastModified)
{
    header("Last-Modified: $lastModified");
    header("ETag: '"{$etag}'"");
    $if_none_match = isset($_SERVER['HTTP_IF_NONE_MATCH']) ?
        stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) : 
        false;
    $if_modified_since = isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) ?
        stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']) :
        false;
    if (!$if_modified_since && !$if_none_match)
        return;
    if ($if_none_match && $if_none_match != $etag && $if_none_match != '"' . $etag . '"')
        return; // etag is there but doesn't match
    if ($if_modified_since && $if_modified_since != $lastModified)
        return; // if-modified-since is there but doesn't match
    // Nothing has changed since their last request - serve a 304 and exit
    header('HTTP/1.1 304 Not Modified');
    exit();
} // doConditionalGet()
// old pond
// a frog jumps
// the sound of water
// —Matsuo Basho
?>

如果您的服务器安装了 imagemagick,则可以像这样完成:

exec("convert $filename -quality 60 -resize x100 -gravity center -crop 100x100+0+0 +repage $newfilename");

还:

使用 GD2 调整大小和裁剪

希望对你有帮助

是的,您可以切换到使用 IMagick 扩展,它在内存处理方面要好得多。正如我在一个老问题中所描述的,GD使用与图像大小成比例的内存,并将其完全未压缩地保存在内存中。