我有一个上传图片并根据方向旋转图片的脚本,我遇到的问题是,当上传包含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信息的图像上。所有其他人上传都没有问题。
可能导致此内存分配问题的原因是什么?
答案 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,以便将来有希望进行优化。