26

Is there any way to remove the EXIF data from a JPG using PHP? I have heard of PEL, but I'm hoping there's a simpler way. I am uploading images that will be displayed online and would like the EXIF data removed.

Thanks!

EDIT: I don't/can't install ImageMagick.

tau
  • 6,499
  • 10
  • 37
  • 60

8 Answers8

20

Use gd to recreate the graphical part of the image in a new one, that you save with another name.

See PHP gd


edit 2017

Use the new Imagick feature.

Open Image:

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

Be sure to keep any ICC profile in the image

    $profiles = $img->getImageProfiles("icc", true);

then strip image, and put the profile back if any

    $img->stripImage();

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

Comes from this PHP page, see comment from Max Eremin down the page.

Déjà vu
  • 28,223
  • 6
  • 72
  • 100
  • 1
    Use `$res = imagecreatefromjpeg($filename)` to load the image, then `imagejpeg ($res, $filename, QUALITY)`, put 100 to Quality - the loss, if any, should be minimal. – Déjà vu Sep 01 '10 at 09:16
  • @ring0: thanks! ill try this as soon as i have the chance (later today). – tau Sep 01 '10 at 18:03
  • 2
    @ring0: sorry for the late response, but this method does indeed work. unfortunately it does recompress, so ill have to figure out what is more important to me as long as i dont have imagemagick installed. – tau Sep 07 '10 at 05:21
  • 1
    yes, this will resample the image and cause minor loss of quality, and potentially do JPEG "magic" to the reds in the image. – tom f Oct 12 '16 at 17:27
  • 1
    Does this also remove image orientation data? My users will likely be uploading images that aren't from 0º device orientation. – CJT3 Jun 15 '21 at 07:20
18

A fast way to do it in PHP using ImageMagick (Assuming you have it installed and enabled).

<?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";
    }   
}
?>
Bill H
  • 2,069
  • 2
  • 20
  • 29
  • 2
    a possible problem with this approach is that stripImage() will also erase color profile related info. Hence image might turn out not how you expect. – maraspin Apr 11 '12 at 15:23
  • 1
    In theory to preserve the colour profile you could do `$profilesArray = $image->getImageProfiles("*",false);` to get an array of the profiles present, then loop through that and run `$image->removeImageProfile('profilename');` unless profilename is 'icc'. Haven't tested it though. – jamesinealing Jan 13 '14 at 16:31
  • Wouldn't it be easier to just do $image->removeImageProfile('exif'); @jamesinealing ? – Codemonkey Apr 05 '16 at 11:22
  • Although the OP specifically said exif, I was presuming they wanted to strip all such profiles for reasons of privacy, file size etc – jamesinealing Apr 06 '16 at 21:49
  • This will also remove the EXIF orientation metadata, which may make some photos appear to the user rotated in the wrong direction. – Flimm Aug 25 '21 at 11:47
16

I was looking for a solution to this as well. In the end I used PHP to rewrite the JPEG with ALL Exif data removed. I didn't need any of it for my purposes.

This option has several advantages...

  • The file is smaller because the EXIF data is gone.
  • There is no loss of image quality (because the image data is unchanged).

Also a note on using imagecreatefromjpeg: I tried this and my files got bigger. If you set quality to 100, your file will be LARGER, because the image has been resampled, and then stored in a lossless way. And if you don't use quality 100, you lose image quality. The ONLY way to avoid resampling is to not use imagecreatefromjpeg.

Here is my function...

/**
 * 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);
}

The code is pretty simple. It opens the input file for reading and the output file for writing, and then starts reading the input file. It data from one to the other. Once it reaches the EXIF marker, it reads the length of the EXIF record and skips over that number of bytes. It then continues by reading and writing the remaining data.

xtempore
  • 5,260
  • 4
  • 36
  • 43
  • Thank you for this! I added `copy($new, $old);` and `unlink($new);` at the end to retain the original file name, very useful bit of code =) – PsychoMantis Feb 01 '19 at 10:36
  • Though this is nice, some images do come back larger. 118k turns into 270k – Shawn Rebelo Nov 22 '19 at 18:30
  • @ShawnRebelo It should be impossible to make it larger. The code literally just reads bytes from one file and writes to another. Assuming it finds the EXIF data it will be smaller. If not it should be the same size. Can you post your image? – xtempore Nov 23 '19 at 21:31
  • @ShawnRebelo only a few images? im thinking about implementing this in my own class, would you recommend? – ii iml0sto1 Nov 23 '19 at 21:57
  • @xtempore I've created a very solid upload class, I would like to remove EXIF data as well and keep the file size the same or smaller. could you explain the code above and what's going on :) also the two parameters, do i pass in the current temp image as old, and what do I pass in new, what are the '0xFFE1 , where do I return the image if I call it as a method with the $_FILES data, etc :) – ii iml0sto1 Nov 23 '19 at 21:59
  • 1
    @iiiml0sto1 I've added some extra comments to the original. The function is based on reading a JPEG file and then writing a new one. If you are working directly from data you'll need to treat it slightly differently, but the idea is the same. Read 2 bytes (=1 word) at a time, find the EXIF marker (0xFFE1), read the next 2 bytes for the length, and skip that many bytes. – xtempore Nov 24 '19 at 04:17
  • This will cause the EXIF orientation data to be deleted, which is not ideal. – Flimm Aug 25 '21 at 11:48
  • 1
    This will skip anything that comes first with an APP1 segment (XMP, Exif, extended XMP, QVCI, FLIR). Additional checking for the signature `"Exif\0\0"` should be done, too. – AmigoJack Sep 27 '21 at 19:46
6

The following will remove all EXIF data of a jpeg file. This will make a copy of original file without EXIF and remove the old file. Use 100 quality not to loose any quality details of picture.

$path = "/image.jpg";

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

(simple approximation to the graph can be found here)

Praxis Ashelin
  • 5,137
  • 2
  • 20
  • 46
  • 2
    "Use 100 quality not to loose any quality details of picture." -- strictly speaking, this is not true. JPEG uses lossy compression, even at 100% quality. – Pocketsand May 17 '16 at 16:14
  • Yes, you are absolutely right Pocketsand, I practically faced it, thats why I use ImageMagick instead of it. – Naveen Web Solutions May 22 '16 at 16:25
  • This doesn't work anymore. I'm using PHP 7. I'll try `ImageMagick` instead like what many are suggesting. – Eje Oct 08 '19 at 02:17
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);
}

It is a prototype for a call from a command line.

Dmitry Bugrov
  • 59
  • 1
  • 3
4

this is the simplest way:

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

foreach($images as $image) {   
    $img = imagecreatefromjpeg($image);
    imagejpeg($img,$image,100);
}
semaca2005
  • 41
  • 1
3

I completely misunderstood your question.

You could use some command line tool to do this job. or write your own php extension to do it. have a look at this lib that would be useful: http://www.sno.phy.queensu.ca/~phil/exiftool/

Cheers,

vfn

vfn
  • 6,026
  • 2
  • 34
  • 45
1

I'm not pretty sure about it, but if its possible using GD o ImageMagick, the first thing that come to my mind is to create a new Image and add the old image to the new one.

Garis M Suero
  • 7,974
  • 7
  • 45
  • 68