php允许内存大小在图像上传轮换时耗尽

时间:2014-06-21 01:36:05

标签: php image memory upload

我有一个上传图片并根据方向旋转图片的脚本,我遇到的问题是,当上传包含​​EXIF标签的图片时,我收到错误消息:

  

允许的内存大小为33554432字节耗尽(尝试分配   10368字节。

然后是错误日志中引用的行。

我注意到它只发生在带有EXIF标签的图像上。如果上传Photoshop或其他内容生成的普通图像,则可以正常工作。

实际的图像方向代码如下:

function correctImageOrientation($fullpath) {
  if (function_exists('exif_read_data')) {
    $exif = exif_read_data($fullpath);
    if($exif && isset($exif['Orientation'])) {
      $orientation = $exif['Orientation'];
      if($orientation != 1){
        $img = imagecreatefromjpeg($fullpath);
        $deg = 0;
        switch ($orientation) {
          case 3:
            $deg = 180;
            break;
          case 6:
            $deg = 270;
            break;
          case 8:
            $deg = 90;
            break;
        }
        if ($deg) {
          $img = imagerotate($img, $deg, 0);        
        }
        // then rewrite the rotated image back to the disk as $filename 
        imagejpeg($img, $fullpath, 100);
      } // if there is some rotation necessary
    } // if have the exif orientation info
  } // if function exists      
}

发生内存问题的error_log中的确切行实际上就是:

$img = imagerotate($img, $deg, 0);

我在脚本中调用它的方式如下:

$dirname = session::value('user_id');
$rotatedfile = '/home/myfolder/public_html/'.$dirname.'/'.$file_name;  
$rotatedfile = $this->correctImageOrientation($rotatedfile);

我基本上想要实现的是旋转的图像保存在与原始文件相同的位置,基本上替换它。

同样,这只发生在包含EXIF信息的图像上。所有其他人上传都没有问题。

可能导致此内存分配问题的原因是什么?

2 个答案:

答案 0 :(得分:4)

您的错误是:

  

允许的内存大小为33554432字节耗尽(尝试分配   10368字节)。

33554432字节转换为32兆字节。所以这就意味着PHP在尝试做一些工作时会耗尽内存。

您声称失败的图片有EXIF信息,但这并不是我的原因。无论如何,您的问题的快速解决方案是通过向您的函数添加ini_set连接到memory_limit的行来增加您的函数的PHP内存

例如,在执行if (function_exists('exif_read_data')) {检查后将其添加到此处。我将其设置为64M,因为在运行此功能时,这将有效地使脚本内存容量加倍。代码在这里:

function correctImageOrientation($fullpath) {
  if (function_exists('exif_read_data')) {
    ini_set('memory_limit', '64M');
    $exif = exif_read_data($fullpath);
    if($exif && isset($exif['Orientation'])) {
      $orientation = $exif['Orientation'];
      if($orientation != 1){
        $img = imagecreatefromjpeg($fullpath);
        $deg = 0;
        switch ($orientation) {
          case 3:
            $deg = 180;
            break;
          case 6:
            $deg = 270;
            break;
          case 8:
            $deg = 90;
            break;
        }
        if ($deg) {
          $img = imagerotate($img, $deg, 0);        
        }
        // then rewrite the rotated image back to the disk as $filename 
        imagejpeg($img, $fullpath, 100);
      } // if there is some rotation necessary
    } // if have the exif orientation info
  } // if function exists      
}
  

我基本上想要实现的是旋转的图像   保存在与原始文件相同的位置,基本上替换它。

问题是你在PHP中使用GD库,当PHP将文件加载到系统中时会耗尽内存,并在尝试旋转图像时占用更多内存。

可能有EXIF信息的图像实际上具有比标准72dpi更高的DPI。因此,虽然它们的尺寸可能看起来与没有EXIF信息的另一图像表面相同,但300dpi图像的尺寸实际上比72dpi图像大4倍。这很可能是为什么这些图像失败的原因;不是EXIF信息,而是整体DPI。

现在,您还可以通过更改显示为php.ini的行来更改memory_limit = 32M中的内存限制。从技术上讲,这可行。但是,对于一个失败的函数脚本,我认为这不是一个好习惯。

那是因为当您更改php.ini中的设置时,它会增加所有PHP交互的RAM;不只是问题问题。所以你的服务器突然占用了更多用于Apache(运行PHP)的基本功能RAM以及消耗更多RAM的奇怪功能。这意味着如果这个代码每天只被访问几次,那么为什么要为每个PHP进程的32M RAM满意的大服务器负担呢?更好地使用ini_set('memory_limit', '64M');来隔离RAM增加需求。

答案 1 :(得分:2)

imagecreatefromjpeg解压缩图像并将结果放入内存。这就是3 MB JPEG有时需要32 MB内存的原因(具体数量取决于分辨率,颜色深度等)。

您需要此结果,因此您将其分配给变量:

$img = imagecreatefromjpeg($fullpath);

现在问题。 imagerotate使用$img资源,旋转它并将结果放入内存的新区域,因为它返回设计新的图像资源而不是覆盖给定的$img资源。所以最后你需要64 MB的内存:

$img = imagerotate($img, $deg, 0);

证明:

// our image
$url = 'https://upload.wikimedia.org/wikipedia/commons/thumb/a/a7/Pluto-01_Stern_03_Pluto_Color_TXT.jpg/1024px-Pluto-01_Stern_03_Pluto_Color_TXT.jpg';
file_put_contents('../cache4/' . basename($url), fopen($url, 'r'));
$filename = '../cache4/' . basename($url);

echo 'Before imagecreate: ' . round(memory_get_usage() / pow(1024, 2)) . ' MB (Max: ' . round(memory_get_peak_usage() / pow(1024, 2)) . ' MB)<br>' . PHP_EOL;

// read image into RAM for further usage
$image = imagecreatefromjpeg($filename);

echo 'After imagecreate: ' . round(memory_get_usage() / pow(1024, 2)) . ' MB (Max: ' . round(memory_get_peak_usage() / pow(1024, 2)) . ' MB)<br>' . PHP_EOL;

// rotate image
$result = imagerotate($image, 180, 0);

echo 'After imagerotate: ' . round(memory_get_usage() / pow(1024, 2)) . ' MB (Max: ' . round(memory_get_peak_usage() / pow(1024, 2)) . ' MB)<br>' . PHP_EOL;

// flip image
imageflip($result, IMG_FLIP_VERTICAL);

echo 'After imageflip: ' . round(memory_get_usage() / pow(1024, 2)) . ' MB (Max: ' . round(memory_get_peak_usage() / pow(1024, 2)) . ' MB)<br>' . PHP_EOL;

返回:

Before imagecreate: 0 MB (Max: 0 MB)
After imagecreate: 5 MB (Max: 5 MB)
After imagerotate: 10 MB (Max: 10 MB)
After imageflip: 10 MB (Max: 10 MB)

正如您所看到的,在imagerotate被调用后,峰值会升至10 MB,但像imageflip这样的函数不会显示此行为,因为它会在内部覆盖您的变量。

也许你认为你可以通过覆盖变量来解决这个问题:

$image = imagerotate($image, 180, 0);

但这只会减少目前的使用量:

After imagerotate: 5 MB (Max: 10 MB)

我们感到遗憾can not pass the variable by reference since PHP 5.4

imagerotate(&$image, 180, 0);

我打开了一个bug report,以便将来有希望进行优化。