PHP允许图像上传旋转耗尽内存大小

我有一个脚本可以上传图片并根据方向旋转它们,我遇到的问题是当上传带有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信息的图像上。 所有其他上传没有问题。

什么可能导致这种内存分配问题?


你的错误是这样的:

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

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

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

例如,在执行if (function_exists('exif_read_data')) { check之后,将其添加到此处。 我将它设置为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。

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

这是因为当你改变php.ini的设置时,它会增加所有PHP交互的RAM; 不只是问题的问题。 所以你的服务器突然吃掉了更多的RAM(用于运行PHP的Apache)用于基本功能,以及古怪的功能,吃掉更多的RAM。 这意味着如果这些代码每天只能访问几次,那么为什么还要为每个PHP进程的32M内存感到满足的大型服务器负载呢? 更好地使用ini_set('memory_limit', '64M'); 以隔离内存增加这样的需求。


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被调用后峰值imagerotate 10 MB,但像imageflip这样的函数不会显示这种行为,因为它会在内部覆盖变量。

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

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

但是这只会减少当前的使用情况:

After imagerotate: 5 MB (Max: 10 MB)

很遗憾,自PHP 5.4以来,我们无法通过引用来传递变量:

imagerotate(&$image, 180, 0);

我打开了一个错误报告,以便将来有望优化。

链接地址: http://www.djcxy.com/p/61151.html

上一篇: php allowed memory size exhausted on image upload rotation

下一篇: PHP resize an image without saving a file