8

Our server is saving EXIF data to every file saved with imagejpeg(). As far as I know, this is not the default behavior (or even possible, from what I've read). But, it is occurring, and due to the FileDateTime information being included (and using the time of save), it is breaking functionality in our upload/approval system (md5_file() returns a different value for the exact same image due to FileDateTime always being different).

Is there a way to prevent imagejpeg() from saving EXIF data for images by default?

Server Information

  • CentOS 5
  • Parallels Plesk Panel 10.4.4
  • GD Version: bundled (2.0.34 compatible)
  • PHP 5.3

Code

<?php
public function upload_book_cover($book, $cover, $filename = NULL, $approved = NULL){
    global $c_consummo, $user;
    $approved = bool($approved, true, true);
    if(filesize($cover)>5242880){
        return false; // Too large;
    }
    $max_width = 450;
    $cover_info = getimagesize($cover);
    if(!$this->is_valid_book_cover_type($cover_info['mime'])){
        return false; // Invalid image type
    }
    $width = $cover_info[0];
    $height = $cover_info[1];
    if($width<200){
        return false; // Too small
    } elseif($width>1500){
        return false; // Too wide
    }
    $original_cover = false;
    switch($cover_info[2]){
        case IMAGETYPE_GIF:
            $original_cover = imagecreatefromgif($cover);
            break;
        case IMAGETYPE_JPEG:
            $original_cover = imagecreatefromjpeg($cover);
            break;
        case IMAGETYPE_PNG:
            $original_cover = imagecreatefrompng($cover);
            break;
        case IMAGETYPE_BMP:
            $original_cover = imagecreatefrombmp($cover);
            break;
    }
    if(!$original_cover){
        return false; // Unsupported type
    }
    if($width>$max_width){
        $new_width = $max_width;
    } else {
        $new_width = $width;
    }
    $new_height = round($height*($new_width/$width));
    $new_cover = imagecreatetruecolor($new_width, $new_height);
    if(!$new_cover){
        return false; // Could not create true color image
    }
    if(!imagecopyresampled($new_cover, $original_cover, 0, 0, 0, 0, $new_width, $new_height, $width, $height)){
        return false; // Could not copy image
    }
    if(!imagejpeg($new_cover, $cover, 100)){
        return false; // Image could not be saved to tmp file
        // This is adding *new* EXIF data to images by itself
    }
    $file_hash = md5_file($cover);
    $duplicate_book_cover = $this->find_duplicate_book_cover($book, $file_hash);
    if($duplicate_book_cover){
        return $duplicate_book_cover;
    }
    $file_id = $c_consummo->upload_file($cover, $filename);
    ...
}
0b10011
  • 18,397
  • 4
  • 65
  • 86
  • 1
    You could hash the original file instead. – Alix Axel May 11 '12 at 15:06
  • @AlixAxel, I don't want to have the EXIF data included (either in the original upload or the new version) as I don't want the file to be considered different because somewhere along the line the EXIF data was changed (without changing the image itself). The whole point of the hash is to avoid storing multiple versions of the same exact image on our server. – 0b10011 May 11 '12 at 15:11
  • Yeah, I get that but since you can't ever check for duplicates right now, comparing the hash of the uploaded image would give you better odds. Anyway, weird problem. Good luck! – Alix Axel May 11 '12 at 15:17
  • @AlixAxel, oh, yeah, that would be a last resort workaround--let's hope it doesn't get to that! Thanks! :) – 0b10011 May 11 '12 at 15:18
  • 1
    Could you try replacing all occurrences of `$cover` (after and including `imagejpeg($new_cover, $cover, 100)`) with `$cover . '.new'` and see if the problem persists? – Alix Axel May 12 '12 at 00:03
  • Possible duplicate on question http://stackoverflow.com/questions/3614925/remove-exif-data-from-jpg-using-php – Bud Damyanov May 16 '12 at 14:13
  • @AlixAxel, bravo, the variable name was the issue. Could you post this as an answer within the next 20 or so mins so I can award the bounty and accept it? – 0b10011 May 16 '12 at 16:11
  • @AlixAxel, and if not, could you please post your answer later on so you can at least get your due credit for figuring out the issue in the code? Thanks! – 0b10011 May 16 '12 at 16:21

10 Answers10

4

It looks like you have tried several things here, but lets try one more. Do you EVER need EXIF information in your application? If not lets take out support for EXIF and see if that completely removes it.

If it does not remove it, then perhaps the functions are reading it from the existing photos and then just blindly including it in the file that is written. You can know for sure by printing out the EXIF information at each step of process

Michael Blood
  • 1,257
  • 10
  • 11
  • 1
    I did print out the EXIF information after each step--it's being updated with the current date & time every time I refresh. And no, I don't need (or want) EXIF information in my application--how do I turn off support for it? – 0b10011 May 11 '12 at 13:42
  • 1
    Perhaps remove the extension from php.ini.http://www.php.net/manual/en/exif.installation.php – Michael Blood May 12 '12 at 17:45
  • 1
    Turned out to be a variable issue as mentioned by AlixAxel in the comments of the question. Thank you for the suggestion though! – 0b10011 May 16 '12 at 16:11
4

No idea why EXIF data is being written - so the following may help you remove it.

One suggestion is to run something as a command on the server - it will need some installation: http://hacktux.com/read/remove/exif - then run throuhg EXEC from PHP.

There's also solution posted here that uses ImageMagick if you also have that installed (or cen get it installed: Remove EXIF data from JPG using PHP (but note warning about colour)

Otherwise, the other suggestion is as above, try turning off the EXIT extension.

Sorry if they don't help, but you did ask for any suggestions.

Community
  • 1
  • 1
Robbie
  • 17,605
  • 4
  • 35
  • 72
2

You could possibly convert the jpeg to a gif first, then convert the gif back to a jpeg. In doing so, my understanding is that you would destroy the EXIF data. It's a hack, but it should work.

0b10011
  • 18,397
  • 4
  • 65
  • 86
pogeybait
  • 3,065
  • 2
  • 21
  • 23
  • I will try it just in case, but the issue isn't EXIF data that is attached to images previously--the server is actually attaching **new** EXIF data to the images (completely ignoring old EXIF data). It's really strange, and seems to be server-specific, but I'm not sure what settings to change. – 0b10011 May 09 '12 at 17:15
  • Converting to a `gif` would also result in a loss of quality, as it's only 256 colors. – Flygenring May 17 '12 at 10:19
2

Read this, maybe it will help:

http://www.php.net/manual/en/function.imagecreatefromjpeg.php#65656

and this:

http://www.sentex.net/~mwandel/jhead/usage.html

Vlad Balmos
  • 3,372
  • 19
  • 34
2

The only thing that spring to mind is, that you could ignore the EXIF data and just make your hash from something else? My suggestion would be to try either hashing the raw output without saving to a file (here outputting in gif format -which makes a smaller, 8-bit image for performance- to an output buffer and hashing the buffer content)

if(!imagecopyresampled(...)) {...}
ob_start();
imagegif($new_cover);
$file_hash = md5(ob_get_contents());
ob_end_clean();
if(!imagejpeg($new_cover, $cover, 100)) {...}

Or you could build a string containing only the pixel information and hash that (here done by accessing each pixel and appending its value to a string, in reverse for performance)

$pixels = '';
for($x=$new_width-1; $x>=0; $x--) {
  for($y=$new_height-1; $y>=0; $y--) {
    $pixels .= imagecolorat($new_cover, $x, $y);
  }
}
$file_hash = md5($pixels);

For performance, you could also choose only to take samples from the image, as this should work just as well or maybe even better (here sampling every 5th pixel of every 5th row)

$pixels = '';
for($x=$new_width-1; $x>=0; $x-=5) {
  for($y=$new_height-1; $y>=0; $y-=5) {
    $pixels .= imagecolorat($new_cover, $x, $y);
  }
}
$file_hash = md5($pixels);

I hope some of this will work (as I can't test it right now) or at least will help you find the way that works for you :)

Flygenring
  • 3,818
  • 1
  • 32
  • 39
  • Great idea, but couldn't solve this issue with it. Will definitely keep it in mind for the future though. As a sidenote, the code you posted was a little broken--I'll edit it in a minute with the corrected version. – 0b10011 May 16 '12 at 16:13
  • I've awarded you the bounty as the person who *did* solve the problem couldn't get an answer posted in time. Also, your answer thought outside of the box, provided a way to ignore EXIF data (without removing it), and worked without having to reconfigure the server. It also led me to realize that there was something else going on other than just the EXIF data, which in turn pointed me to the answer posted in the comments on my question. So, thank you! – 0b10011 May 16 '12 at 16:33
  • I'm glad you liked my suggestions :) it's what I usually do if I run into a problem with stuff that doesn't work quite as it should according to the documentation - try to find a different solution that doesn't require the part that doesn't work. I'm sorry the proper solution to the real problem wasn't posted in time, but thank you for the bounty! Also it might be nice to make a comment of it in the PHP docs for imagejpeg()? The reason I made the loop in my second code run backwards was purely performance related, but forgot to subtract one. I will just correct this :) – Flygenring May 17 '12 at 10:31
2

Apparently, GD doesn't like when the path to the input / output file is the same, but the credit isn't mine. To fix, use a new (tmp) file to save the newly created image to:

<?php
...
if(!imagecopyresampled($new_cover, $original_cover, 0, 0, 0, 0, $new_width, $new_height, $width, $height)){
 return false; // Could not copy image
}
// Create a tmp file.
$cover_new = tempnam('/tmp', 'cover-');
// Use $cover_new instead of $cover
if(!imagejpeg($new_cover, $cover_new, 100)){
 return false; // Image could not be saved to tmp file
}
// Use $cover_new instead of $cover
$file_hash = md5_file($cover_new);
$duplicate_book_cover = $this->find_duplicate_book_cover($book, $file_hash);
if($duplicate_book_cover){
 return $duplicate_book_cover;
}
// Use $cover_new instead of $cover
$file_id = $c_consummo->upload_file($cover_new, $filename);
...
Community
  • 1
  • 1
Alix Axel
  • 151,645
  • 95
  • 393
  • 500
1

This is a bit of a hack, but it will work, just do this after your switch() statement:

$original_cover = imagerotate($original_cover,360,0);

GD will strip any EXIF data out, as it doesn't support it.

0b10011
  • 18,397
  • 4
  • 65
  • 86
Dave
  • 1,420
  • 3
  • 17
  • 25
  • This doesn't work, sorry! (And I didn't think GD supported it either, hence my confusion.) – 0b10011 May 16 '12 at 14:36
  • Maybe you need to do it to $new_cover and $cover? Might be worth a try. I think I misunderstood your question when I explained where to put it. – Dave May 16 '12 at 14:43
  • This is weird! Do you have access to ImageMagick's convert program on the server? I think that's your best bet. – Dave May 16 '12 at 15:24
  • I know! And yeah, I think that may be what I have to resort to. Hoping for another solution, however. – 0b10011 May 16 '12 at 15:48
0

You could try using imagecreatefromjpeg on the image created with imagejpeg and replacing with the newly created one.

0

$res = imagecreatefromjpeg($filename) to load the image, then imagejpeg($res, $filename, QUALITY)to rerender it.

You can use imagemagick too:

$img = new Imagick($image);
    $img->stripImage();
    $img->writeImage($image);
    $img->clear();
    $img->destroy();
Moritz
  • 153
  • 1
  • 9
0

Your PHP must be compiled in with --enable-exif.

Try to disable globally EXIF functionality by recompiling PHP without this option.

0b10011
  • 18,397
  • 4
  • 65
  • 86
Bud Damyanov
  • 30,171
  • 6
  • 44
  • 52