Does Imagick read iPhone Exif:Orientation wrong? - php

I try to autorotate some images from a zip with php. For the autorotate I'am using the Exif:Orientation informations of the image.
The code does work, but I have problems with some images which seems to have wrong exif informations. I have this problem only with images taken by an iPhone..
What my Code do:
get the picture out of the zip as a string
save the pic in a tempfile for .png handling
init an imagick object with the picture string from 1.
check if the pic is a png
is png: read the exif orientation via helper class PNGMetadata
is not png: use imagick imageproperty to get the exif orientation
call autoRotateImage to turn the pic into the right orientation
save the original orientation for later (I'am saving the org. orientations in a db for debugging)
The function "autoRotateImage" only handle the exif orientation and do the correct steps for each.
Parts of the php code:
try{
$img_content = $zip->getFromIndex($zip->locateName($img->getAttribute('xlink:href')));
$tmpPic = tempnam(sys_get_temp_dir(), $img->getAttribute('xlink:href'));
file_put_contents($tmpPic,$img_content);
$imagick_img = new \Imagick();
$imagick_img->readImageBlob($img_content);
if (PNGMetadata::isPNG($tmpPic)){
$png_metadata = new PNGMetadata($tmpPic);
$orientation_arr[$img->getAttribute('xlink:href')] = $png_metadata->get('exif:IFD0:Orientation');
$org_orientation = $png_metadata->get('exif:IFD0:Orientation');
}else{
$orientation_arr[$img->getAttribute('xlink:href')] = intval($imagick_img->getImageProperty('Exif:Orientation'));
$org_orientation = intval($imagick_img->getImageProperty('Exif:Orientation'));
}
autoRotateImage($imagick_img,new \Imagick(),(isset($manualOrientation[$img->getAttribute('xlink:href')]) ? $manualOrientation[$img->getAttribute('xlink:href')] : $org_orientation));
$imagick_img->clear();
unlink($tmpPic);
}catch(Exception $e){
if(isset($imagick_img)){$imagick_img->clear();}
if(isset($tmpPic)){unlink($tmpPic);}
}
autoRotateImage function:
function autoRotateImage(&$image, $imagick, $orientation) {
switch($orientation) {
case $imagick::ORIENTATION_BOTTOMRIGHT: //3
$image->rotateimage("#000", 180); // rotate 180 degrees
break;
case $imagick::ORIENTATION_BOTTOMLEFT: //4
$image->flipImage(); // mirror vertical
break;
case $imagick::ORIENTATION_RIGHTTOP: //6
$image->rotateimage("#000", 90); // rotate 90 degrees CW
break;
case $imagick::ORIENTATION_LEFTTOP: //5
$image->transverseImage(); // horizontal mirror image by reflecting the pixels around the central y-axis while rotating them 270-degrees.
break;
case $imagick::ORIENTATION_LEFTBOTTOM: //8
$image->rotateimage("#000", 270); // rotate 270 degrees CW
break;
case $imagick::ORIENTATION_RIGHTBOTTOM: //7
$image->transverseImage(); //horizontal mirror image by reflecting the pixels around the central y-axis while rotating them 270-degrees.
$image->rotateimage("#000", -180); // rotate 180 degrees CCW
break;
case $imagick::ORIENTATION_TOPLEFT: //1
//Nothing to do
break;
case $imagick::ORIENTATION_TOPRIGHT: //2
$image->transverseImage(); //horizontal mirror image by reflecting the pixels around the central y-axis while rotating them 270-degrees.
$image->rotateimage("#000", 90); // rotate 90 degrees CW
break;
}
// Now that it's auto-rotated, make sure the EXIF data is correct in case the EXIF gets saved with the image!
$image->setImageOrientation($imagick::ORIENTATION_TOPLEFT);
}
The class "PNGMetadata" is from someone else: https://github.com/joserick/PNGMetadata
So.. this code works fine for like 90% of handled images, except some iPhone images.
Current case: Exif Orientation says 5 (mirror at y and turn 270 CW), but it should be 7 (mirror at y and turn 90 CW)
Maybe someone have an idea how to handle those cases?
I already tried to get the exif information in other forms, but the result is everytime the same.
Also an commandline exif reader read the wrong exif orientation out of the faulty images.
I need a way to fix the wrong orientations..

It seems that the imagick function "transverseImage()" doesn't work as aspected..
I don't know exactly what it do, but I think the behaviour of it doesn't fit the description..
The desc says "Creates a horizontal mirror image by reflecting the pixels around the central y-axis while rotating them 270-degrees.". For me that means, that the Image get mirrored and after that rotated by 270°CW. But Images will be false rotated after that func.
I tried "Imagick::flopImage" in combination with rotateImage(270) and get different results.
Short: I replaced transverseImage with flopImage + rotateImage and now it seems, that the Images getting rotated correctly.

Related

PHP ImageMagick functions rotating phone images inexplicably

I was using this call to imageMagick in PHP to merge multiple images horizontally
$combined_image = $images2merge->appendImages(false);
And it works great when I upload the images from my laptop. But when I go to the same page with my android phone browser and use the camera to take the pics to upload, this function ends up merging them vertically.
So I tried to swap it out with this function
$combined_image = $images2merge->montageImage($draw, '6×6+0+0', '+1+1', Imagick::MONTAGEMODE_UNFRAME, 0);
And that does now merge them horizontally when I use my phone, but it rotates them 90 degrees counterclockwise. Again that's different from what happens when I upload from my laptop, in which case it works as expected. It's hard to fix something that behaves inconsistently.
Any suggestions on how to fix this would be greatly appreciated!
After taking photos from any smartphone, please check the orientation value and rotate the image accordingly before further image processing.
So you can use the following autoRotateImage function to handle the auto-rotation:
<?php
function autoRotateImage($image) {
$orientation = $image->getImageOrientation();
switch($orientation) {
case imagick::ORIENTATION_BOTTOMRIGHT:
$image->rotateimage("#000", 180); // rotate 180 degrees
break;
case imagick::ORIENTATION_RIGHTTOP:
$image->rotateimage("#000", 90); // rotate 90 degrees CW
break;
case imagick::ORIENTATION_LEFTBOTTOM:
$image->rotateimage("#000", -90); // rotate 90 degrees CCW
break;
}
// Now that it's auto-rotated, make sure the EXIF data is correct in case the EXIF gets saved with the image!
$image->setImageOrientation(imagick::ORIENTATION_TOPLEFT);
}
?>
For example:
<?php
$image = new Imagick('my-image-file.jpg');
autoRotateImage($image);
// - Do other stuff to the image here -
$image->writeImage('result-image.jpg');
?>

ImageMagick and PHP to properly rotate an image

I am trying to use Imagick in my php code to properly orient images, so that I can strip the metadata from them and have them properly show up. I tried the methodology in this post:
Detect EXIF Orientation and Rotate Image using ImageMagick
But it is not working. Using the autorotate function in that post, this is what I am doing:
$working_image = new \Imagick();
$working_image->readImageBlob( $source_data);
$working_image->setImageFormat('jpeg');
autorotate($working_image);
... some resizing code:
$working_image->resizeImage( $width, $height, \Imagick::FILTER_CATROM, .7);
// get rid of metadata
$working_image->stripImage();
$working_image->writeImage( <unique filename> );
$working_image->getImageBlob();
... write out to data file to google
We're using Google's cloud stuff to store our files, hence the "getImageBlob" call.
Problem is that this doesn't seem to work. The resultant images are still oriented the same way they were, but now have no metadata, so the tag won't "fix" them.
What am I doing wrong here? I am using this version of the Imagick PHP object:
[versionNumber] => 1673
[versionString] => ImageMagick 6.8.9-9 Q16 x86_64 2015-01-05 http://www.imagemagick.org
In response to your answer:
Thanks for the clarifications. Looking at one of the specific images that I am having problems with, identify -verbose shows multiple places defining orientation:
Orientation: RightTop
exif:Orientation: 6
exif:thumbnail:Orientation: 6
And this is reflected in my call to Imagick::getImageOrientation in PHP, which returns the "6" value, which then causes my code to call functions like "Imagick::rotateImage("#000", 180);". The problem is that the image is unchanged by these calls. 8-(
The core problem is this: We get these images by the thousands every day from MLS's all over the country. We currently process them (resizing currently) via an automatic process. When they are displayed on our site, everything is currently fine, because the <img> tag seems to be happy to interpret the EXIF data and show the image in proper orientation. BUT... we want to start optimizing images, which means stripping the EXIF info out. So I need to figure out why Imagick->rotateImage() isn't working.
Ok, let's write a whole answer:)
What I meant with the link to ImageMagick's -auto-orient, was to point out, that this kind of auto-orientation depends on EXIF profile and "Orientation" setting. If it's missing or is wrong the auto-orientation will not work as expected. To check whether your images has the EXIF profile with Orientation in tact, you can use several ways:
PHP Imagick (check it with image before you call ->stripImage())
echo 'Orientation is: ', $working_image->getImageOrientation();
PHP (if you have local file)
$exif = exif_read_data('input.jpg');
echo 'Orientation is: ', isset($exif['Orientation']) ? $exif['Orientation'] : 'missing';
Online EXIF reader.
The values you get are described for example here or more about image auto-rotation topic and sample images are here
Or you can use programs like Gimp or Photoshop.
So you're doing nothing wrong, without EXIF this won't work. It's camera that is writing the tags into images, so there is no guarantee all your photos have it.
Perhaps offer image rotation for visitors in your website instead? There is now CSS3 transform: rotate(deg) property that makes it really easy, see....
We can get the current image orientation and can easily update to the original one with Imagick
public function fix_image_orientation()
{
$working_image = new \Imagick();
$working_image->readImageBlob($source_data);
$working_image->setImageFormat('jpeg');
$orientation = $working_image->getImageOrientation();
if (!empty($orientation)) {
switch ($orientation) {
case imagick::ORIENTATION_BOTTOMRIGHT:
$working_image->rotateimage("#000", 180);
break;
case imagick::ORIENTATION_RIGHTTOP:
$working_image->rotateimage("#000", 90);
break;
case imagick::ORIENTATION_LEFTBOTTOM:
$working_image->rotateimage("#000", -90);
break;
}
}
$working_image->setImageOrientation(imagick::ORIENTATION_TOPLEFT);
$working_image->writeImage( <unique filename> );
}
#Satendra Rawat posted a good, but incomplete answer. There is the GitHub repository https://github.com/ianare/exif-samples, where you can see 8 orientation examples. Only 3, 5 and 8 worked correctly.
So here is a update for all the orientations:
public function fix_image_orientation()
{
$working_image = new \Imagick();
$working_image->readImageBlob($source_data);
$working_image->setImageFormat('jpeg');
$orientation = $working_image->getImageOrientation();
if (!empty($orientation)) {
switch ($orientation) {
default:
case imagick::ORIENTATION_TOPLEFT:
break;
case imagick::ORIENTATION_TOPRIGHT:
$working_image->flipImage();
$working_image->rotateImage("#000", 180);
break;
case imagick::ORIENTATION_BOTTOMRIGHT:
$working_image->rotateImage("#000", 180);
break;
case imagick::ORIENTATION_BOTTOMLEFT:
$working_image->flipImage();
break;
case imagick::ORIENTATION_LEFTTOP:
$working_image->rotateImage("#000", -90);
$working_image->flipImage();
break;
case imagick::ORIENTATION_RIGHTTOP:
$working_image->rotateImage("#000", 90);
break;
case imagick::ORIENTATION_RIGHTBOTTOM:
$working_image->rotateImage("#000", 90);
$working_image->flipImage();
break;
case imagick::ORIENTATION_LEFTBOTTOM:
$working_image->rotateImage("#000", -90);
break;
}
}
$working_image->setImageOrientation(imagick::ORIENTATION_TOPLEFT);
$working_image->writeImage( <unique filename> );
}

PHP - Detect if image has been manually rotated, EXIF Orientation flag wrong

I’m experiencing some strange issues with image rotation.
Originally we were having the problem of some users uploading images onto our site but they were appearing the wrong way around. We therefore wanted to implement some code to check the EXIF Orientation flag and rotate the image if necessary.
Here is what the simplified function looks like:
function image_fix_orientation($filename) {
$exif = exif_read_data($filename);
if (!empty($exif['Orientation'])) {
$image = imagecreatefromjpeg($filename);
switch ($exif['Orientation']) {
case 3:
$image = imagerotate($image, 180, 0);
break;
case 6:
$image = imagerotate($image, -90, 0);
break;
case 8:
$image = imagerotate($image, 90, 0);
break;
}
imagejpeg($image, $filename, 100);
}
}
This seems to work fine for the images which have the Orientation flag set, were rotated and causing the original issue.
However, bizarrely I have found a number of images previously uploaded onto our server (we retain the original image with EXIF data intact) from before we have implemented the above code which appear to contain an Orientation flag (of say ‘3’) but their raw picture is the correct way up. If I feed one of these images into my new upload code it reads the Orientation flag and rotates the image, but the image was the correct way around to begin with.
I believe the user has likely manually rotated the image prior to upload yet it’s still retaining the Orientation flag value. For example, if I rotate an image in Paint it seems to retain the same Orientation value. Here is an example image http://s16.postimg.org/cv5ejchqt/exig.jpg (arrow points to the true top of the image), yet it has the Orientation flag value of 3 so is rotated when using my new upload code.
Is there a way to detect if the user has manually rotated, somehow?
Thanks

No orientation in exif data - PHP image upload

been trying to detect the image orientation of uploaded images from iPhones and then adjust their orientation from that.
I am trying to fix the issue where images taken in potrait, are uploaded with a -90 degree rotate. I tried numerous switch statements which were not working, so decided to return the exif data in my JSON return.
The issue i see is that their is no orientation in the exif data.
I am doing so:
$imagefile = $fileToUpload["tmp_name"];
$destinationImage = imagecreatefromstring(file_get_contents($imagefile));
$exif = exif_read_data($imagefile);
$moveUploadedFile = imagejpeg($destinationImage, $this->uploadDir . "/" . $newFileName, 100);
imagedestroy($destinationImage);
if ($moveUploadedFile) {
$return['ort'] = $exif;
echo json_encode($return);
}
What i am seeing in my return (using firebug) is:
FileName:"phpUQZFHh"
FileDateTime:1410465904
FileSize:473421
FileType:2
MimeType:"image/jpeg"
SectionsFound:"COMMENT"
Computed: OBJECT:
Height:700
Width:933
IsColor:1
Comment: ARRAY:
0:"CREATOR: gd-jpeg v1.0 (using IJG JPEG v62), quality = 100"
I want to be able use the exif data like so:
if (!empty($exif['Orientation'])){
//get the orientation
$ort = $exif['Orientation'];
//determine what oreientation the image was taken at
switch($ort){
case 2: // horizontal flip
break;
case 3: // 180 rotate left
$destinationImage = imagerotate($destinationImage, 180, -1);
break;
}
}
Any help?
EDIT: After downloaded an image that had been uploaded and checking its properties it appears that all exif data was removed in the upload process.
This still baffles me as to why it is rotated before / during upload / how to fix this.
I guess the "Orientation" value presents in the returned data of exif_read_data function in case when you upload the picture from your iOS device only. It won't work in desktop browser. I might be wrong.
I ran into the same problem. Turns out some images really did not have any exif data on Orientation at all -- usually ones with the "correct" orientation do not have it. I tried one landscape image taken with an iPhone and there was.
In your case, the photos may have had no exif data in the first place. I had some photos like that as well (rotated -90 degrees but no Orientation info). I could be wrong but without exif data, there's no programmatic way to know if an image is incorrectly oriented.
For incorrectly oriented photos without Orientation info, I suggest you just make sure the user sees (gets a preview) of what about to be uploaded. IME, most users are more than willing to get out of their way to fire up paint/photoshop/etc. just to ensure they have good looking photos.
You can get Orientation value before move the file to the server directory (Worked with iPhone as well)
$image = $_FILES["image"]["tmp_name"];
$orientation = '';
if (function_exists('exif_read_data'))
{
$exif = exif_read_data(image);
if($exif && isset($exif['Orientation']))
$orientation = $exif['Orientation'];
}

How to optimize images in PHP?

I've a website where users can save their profile image (avatar).
I'd like to make some optimization to the image when they load it.
I mean, it's an avatar image, doesn't need full resolution, nor great size.
What can i do? I've been thinking:
Resize it
Low the quality
Possible:
convert it to GIF
Color fill to transparent PNGs
There are some library better (simpler) than GD to do this?
Thanks a lot!
GD is how this is done. It sounds like a simple operation, but there are a number of factors that you really want to get right if you're going to do this. All in all, this winds up being several hundred lines of code to take care of everything.
My recommendation is that although you may wish to resize an image (which requires a lossy recompression if using JPEG), converting it to a GIF is a bad idea. You don't know what the source type is, so doing that is problematic.
Here's my recommended flow:
1) Resize the image to your output format. You can force a cropping aspect ratio here as well, if you want.
2) Determine original source mode:
8 bit indexed (GIF/PNG8): Save as PNG8 (format tends to be smaller than GIF).
16-24 bit: Save as JPG. Quality is up to you, but 70% is a good baseline.
32 bit (PNG24): Save as PNG24, taking care to maintain the transparency.
Note, this solution pretty much destroys any 'animated' gifs, but... that's what happens when you try to resize an animated gif.
Although... I also highly recommend to NOT do this as a single stage process and removing the original files. This is the kind of thing that will only come back to bite you later.
Disk space is cheap these days... far better to store the original in a high quality format (even at 2K x 2K resolution), then create an image service which will serve the resolution/quality you need and cache the result.
You could use the Asido imaging library for PHP to resize your images. This library makes use of GD though. Here is some example usage code.
The resizing and other imaging operations are preferably done after the uploading of new images (except if you want to save the higher resolution for some other purpose).
<p>
//This function will proportionally resize image
function resizeImage($CurWidth,$CurHeight,$MaxSize,$DestFolder,$SrcImage,$Quality,$ImageType)
{
//Check Image size is not 0
if($CurWidth <= 0 || $CurHeight <= 0)
{
return false;
}
//Construct a proportional size of new image
$ImageScale = min($MaxSize/$CurWidth, $MaxSize/$CurHeight);
$NewWidth = ceil($ImageScale*$CurWidth);
$NewHeight = ceil($ImageScale*$CurHeight);
$NewCanves = imagecreatetruecolor($NewWidth, $NewHeight);
// Resize Image
if(imagecopyresampled($NewCanves, $SrcImage,0, 0, 0, 0, $NewWidth, $NewHeight, $CurWidth, $CurHeight))
{
switch(strtolower($ImageType))
{
case 'image/png':
imagepng($NewCanves,$DestFolder);
break;
case 'image/gif':
imagegif($NewCanves,$DestFolder);
break;
case 'image/jpeg':
case 'image/pjpeg':
imagejpeg($NewCanves,$DestFolder,$Quality);
break;
default:
return false;
}
//Destroy image, frees memory
if(is_resource($NewCanves)) {imagedestroy($NewCanves);}
return true;
}
}
</p>
I'd pick some standard avatar image sizes you'll need for your page, like
medium size for a profile page, if you have one and
small size which appears next to the user's post
you get the idea, just what you need
And when the user uploads a new avatar, you convert it to the formats you'll need with a reasonable quality setting. I'm assuming you're going for JPEGs, because this is a good catch-all format for this use case. PNGs do poor with photographic content, JPEGs are not so great for drawings, but then most avatars you see are photos. I wouldn't use GIFs any more these days, they limit to 256 colors and have only a 1-bit alpha channel.

Categories