【问题标题】:php allowed memory size exhausted on image upload rotationphp允许在图像上传旋转时耗尽内存大小
【发布时间】:2014-08-11 19:33:52
【问题描述】:

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

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

【问题讨论】:

    标签: php image memory upload


    【解决方案1】:

    你的错误是这样的:

    允许的内存大小为 33554432 字节已用尽(试图分配 10368 字节)。

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

    您声称失败的图像具有 EXIF 信息,但这对我来说并不是真正的原因。无论如何,快速解决您的问题的方法是通过将连接到 memory_limitini_set 行添加到您的函数来增加函数的 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。

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

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

    【讨论】:

    • 哇,太奇怪了。因为我实际上认为它是一个 memory_limit 问题,我在 php.ini 中将它增加到 256MB,但是在看到你的代码修复它之后,我再次对 php.ini 进行了 grep(我以前没有这样做过) ) 并注意到有 2 个不同的 memory_limit 集。我多么愚蠢!非常感谢您帮助解决此问题:)
    • @user1227914 太好了!另外,请查看我在代码下方的最新编辑,该编辑解释了为什么我认为您的 RAM 被占用了这么多,而在这种情况下调整 php.ini 可能不是一个好主意。
    • 这是有道理的,我会实施你的建议。非常感谢您非常有帮助的回复:)
    【解决方案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,希望以后能优化。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-06-25
      • 1970-01-01
      • 2017-12-13
      • 2018-08-04
      • 2012-01-10
      • 1970-01-01
      相关资源
      最近更新 更多