使用PHP从JPG中删除EXIF数据

26

有没有办法使用PHP从JPG中删除EXIF数据?我听说过PEL,但希望有更简单的方法。我正在上传将在线显示的图像,并希望删除EXIF数据。

谢谢!

编辑:我不想/不能安装ImageMagick。

8个回答

20

使用gd在一个新图像中重新创建图像的图形部分,并将其以另一个名称保存。

请参见PHP gd


编辑2017

使用新的Imagick功能。

打开图像:

<?php
    $incoming_file = '/Users/John/Desktop/file_loco.jpg';
    $img = new Imagick(realpath($incoming_file));

请确保将图像中的任何ICC配置文件保留。

    $profiles = $img->getImageProfiles("icc", true);
然后剥离图片,如果有的话则将配置文件放回原处。
    $img->stripImage();

    if(!empty($profiles)) {
       $img->profileImage("icc", $profiles['icc']);
    }

这段内容来源于PHP官方页面,请参考页面底部Max Eremin的评论。


1
使用 $res = imagecreatefromjpeg($filename) 加载图像,然后 imagejpeg ($res, $filename, QUALITY),将 Quality 设置为 100 - 如果有任何损失,应该是最小的。 - Déjà vu
@ring0:谢谢!我一有机会就会尝试这个(今天晚些时候)。 - tau
2
@ring0:抱歉回复晚了,但这种方法确实有效。不幸的是它会重新压缩,所以我必须想清楚什么对我更重要,只要我没有安装ImageMagick。 - tau
1
是的,这将重新采样图像并导致轻微的质量损失,并可能对图像中的红色进行JPEG“魔法”处理。 - tom f
1
这是否也会删除图像方向数据?我的用户可能会上传不是从0º设备方向拍摄的图像。 - CJT3

18

使用ImageMagick在PHP中快速完成此操作(假设您已安装并启用它)。

<?php

$images = glob('*.jpg');

foreach($images as $image) 
{   
    try
    {   
        $img = new Imagick($image);
        $img->stripImage();
        $img->writeImage($image);
        $img->clear();
        $img->destroy();

        echo "Removed EXIF data from $image. \n";

    } catch(Exception $e) {
        echo 'Exception caught: ',  $e->getMessage(), "\n";
    }   
}
?>

2
这种方法可能存在的问题是stripImage()也会擦除与颜色配置文件相关的信息。因此,图像可能不会如您所期望的那样显示。 - maraspin
1
理论上,为了保留颜色配置文件,您可以执行 $profilesArray = $image->getImageProfiles("*",false); 以获取存在的配置文件数组,然后循环遍历该数组并运行 $image->removeImageProfile('profilename');,除非 profilename 是 'icc'。虽然我没有测试过。 - jamesinealing
@jamesinealing,直接使用 $image->removeImageProfile('exif') 不是更简单吗? - Codemonkey
尽管OP明确提到了exif,但我认为他们想要出于隐私、文件大小等原因剥离所有这样的配置文件。 - jamesinealing
这也将删除EXIF方向元数据,这可能会使一些照片在用户看来旋转到错误的方向。 - Flimm

16

我也在寻找解决方法。最终,我使用PHP重写了JPEG图像,并删除了所有的Exif数据。对于我的目的,我不需要这些数据。

这个选项有几个优点...

  • 由于EXIF数据已经被删除,文件变得更小了。
  • 没有图像质量损失(因为图像数据没有改变)。

另外,关于使用imagecreatefromjpeg的注意事项:我尝试过这个函数,但我的文件变大了。如果您将质量设置为100,您的文件会变大,因为图像已经被重新采样,然后以无损方式存储。如果您不使用质量100,那么就会失去图像质量。避免重新采样的唯一方法是不使用imagecreatefromjpeg。

这是我的函数...

/**
 * Remove EXIF from a JPEG file.
 * @param string $old Path to original jpeg file (input).
 * @param string $new Path to new jpeg file (output).
 */
function removeExif($old, $new)
{
    // Open the input file for binary reading
    $f1 = fopen($old, 'rb');
    // Open the output file for binary writing
    $f2 = fopen($new, 'wb');

    // Find EXIF marker
    while (($s = fread($f1, 2))) {
        $word = unpack('ni', $s)['i'];
        if ($word == 0xFFE1) {
            // Read length (includes the word used for the length)
            $s = fread($f1, 2);
            $len = unpack('ni', $s)['i'];
            // Skip the EXIF info
            fread($f1, $len - 2);
            break;
        } else {
            fwrite($f2, $s, 2);
        }
    }

    // Write the rest of the file
    while (($s = fread($f1, 4096))) {
        fwrite($f2, $s, strlen($s));
    }

    fclose($f1);
    fclose($f2);
}

这段代码相当简单。它打开输入文件以进行读取,并打开输出文件用于写入,然后开始读取输入文件的数据并将其写入输出文件中。一旦它到达EXIF标记,它会读取EXIF记录的长度并跳过相应数量的字节。然后它继续读取和写入剩余的数据。


谢谢您!我在结尾处添加了 copy($new, $old);unlink($new); 以保留原始文件名,这是非常有用的代码段 =) - PsychoMantis
@ShawnRebelo 这是不可能变大的。这段代码只是从一个文件中读取字节并写入另一个文件。假设它找到了EXIF数据,那么它会变小。如果没有找到,那么它的大小应该保持不变。你能贴出你的图片吗? - xtempore
1
@iiiml0sto1 我已经在原始代码中添加了一些额外的注释。该函数基于读取JPEG文件,然后编写一个新文件。如果您直接从数据中工作,则需要稍微不同地处理它,但是思路是相同的。每次读取2个字节(= 1个字),查找EXIF标记(0xFFE1),读取下2个字节以获取长度,并跳过那么多字节。 - xtempore
这将导致删除EXIF方向数据,这并不理想。 - Flimm
1
这将跳过任何以APP1段(XMP、Exif、扩展XMP、QVCI、FLIR)开头的内容。还应该进行签名“Exif\0\0”的额外检查。 - AmigoJack
显示剩余3条评论

6
function remove_exif($in, $out)
{
    $buffer_len = 4096;
    $fd_in = fopen($in, 'rb');
    $fd_out = fopen($out, 'wb');
    while (($buffer = fread($fd_in, $buffer_len)))
    {
        //  \xFF\xE1\xHH\xLLExif\x00\x00 - Exif 
        //  \xFF\xE1\xHH\xLLhttp://      - XMP
        //  \xFF\xE2\xHH\xLLICC_PROFILE  - ICC
        //  \xFF\xED\xHH\xLLPhotoshop    - PH
        while (preg_match('/\xFF[\xE1\xE2\xED\xEE](.)(.)(exif|photoshop|http:|icc_profile|adobe)/si', $buffer, $match, PREG_OFFSET_CAPTURE))
        {
            echo "found: '{$match[3][0]}' marker\n";
            $len = ord($match[1][0]) * 256 + ord($match[2][0]);
            echo "length: {$len} bytes\n";
            echo "write: {$match[0][1]} bytes to output file\n";
            fwrite($fd_out, substr($buffer, 0, $match[0][1]));
            $filepos = $match[0][1] + 2 + $len - strlen($buffer);
            fseek($fd_in, $filepos, SEEK_CUR);
            echo "seek to: ".ftell($fd_in)."\n";
            $buffer = fread($fd_in, $buffer_len);
        }
        echo "write: ".strlen($buffer)." bytes to output file\n";
        fwrite($fd_out, $buffer, strlen($buffer));
    }
    fclose($fd_out);
    fclose($fd_in);
}

这是一个命令行调用原型。


太棒了!不需要使用 imagecreatefromjpeg - kodfire

6
以下内容将移除jpeg文件的所有EXIF数据。这将创建一个不含EXIF信息的原始文件副本,并删除旧文件。使用100质量以不丢失任何图片细节。
$path = "/image.jpg";

$img = imagecreatefromjpeg ($path);
imagejpeg ($img, $path, 100);
imagedestroy ($img);

这里提供一个简单的图形近似,可以在这里找到。


2
使用100%的质量,不会丢失任何图片细节。 严格来说,这并不是真的。JPEG即使在100%的质量下也使用有损压缩。 - Pocketsand
是的,Pocketsand,你说得完全正确,我实际上也遇到了这个问题,这就是为什么我使用ImageMagick而不是它的原因。 - Naveen Web Solutions
这个不再起作用了。我正在使用PHP 7。我将尝试像许多人建议的那样改用ImageMagick - Eje

4
这是最简单的方法:
$images = glob($location.'/*.jpg');

foreach($images as $image) {   
    $img = imagecreatefromjpeg($image);
    imagejpeg($img,$image,100);
}

2
但文件会相当大……比原始文件更大。 - ii iml0sto1

3

我完全误解了你的问题。

你可以使用一些命令行工具来完成这项工作,或者编写自己的php扩展来完成。看一下这个相当有用的库:http://www.sno.phy.queensu.ca/~phil/exiftool/

祝好运,

vfn


谢谢,但我不明白如何使用exif.php来删除数据。 - tau

1

我不是很确定,但如果可能的话,可以使用GDImageMagick,我首先想到的是创建一个新图像并将旧图像添加到新图像中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接