views:

855

answers:

4

I used F-Spot on Ubuntu to rotate some photos (JPEG files) before I FTPed them up to my website. This seemed to work just fine. However, if those images are opened in a web browser, they do not show as rotated. Nor do they if I download them to a Windows Vista machine and open them with any standard program there. I suspect that F-Spot rotates images by modifying the exif data or similar, not by actually rotating the images.

So I want a little function which will run on my web server (i.e., PHP or Perl) which will accept an array of file paths, examine the images, and rotate those which need to be rotated, overwriting the original file.

I know some PHP but no Perl.


In the course of searching to see whether this question had already been asked, I came across some ideas. I might be able, after some trial and error, to knock something together using glob(), exif_read_data(), and imagerotate(). I'll try tomorrow. But now I'm going to bed.

+3  A: 

In Perl you can rotate images using the Image::Magick module. There's a PHP interface too, and a command-line interface (I think). If you're just rotating a few images you're probably best off with the command line version.

Here's a simple Perl script to rotate images clockwise (and preserves the files' modification time):

use strict;
use warnings;
use Image::Magick;

die "no filename specified!\n" if not @ARGV;

foreach my $filename (@ARGV)
{
    print "Processing: $filename\n";

    # Get the file's last modified time for restoring later
    my $mtime = (stat $filename)[9];

    my $image = Image::Magick->new;
    my $result = $image->Read($filename);
    warn "$result" if $result;
    $result = $image->Rotate(degrees => 90.0);
    warn "$result" if $result;
    $result = $image->Write($filename);
    warn "$result" if $result;

    # Restore the mtime
    utime time, $mtime, $filename;
}
Ether
you don't need to store all of the stat bits you don't need -- you could do `my $mtime = (stat $filename)[9]` or you could load `File::stat` (core for the past 12 years) and do `my $mtime = stat($filename)->mtime`.
hobbs
Agreed! (I pulled this out of a script I wrote ~10 years ago, so it's definitely less than perfect, and I had to make lots of edits to it before it was fit to post.. e.g. I can't believe I didn't use `use strict; use warnings;`) :D
Ether
+3  A: 

Copying this directly from the PHP website: http://us.php.net/manual/en/function.imagerotate.php

This example rotates an image 180 degrees - upside down.

<?php
// File and rotation
$filename = 'test.jpg';
$degrees = 180;

// Content type
header('Content-type: image/jpeg');

// Load
$source = imagecreatefromjpeg($filename);

// Rotate
$rotate = imagerotate($source, $degrees, 0);

// Output
imagejpeg($rotate);
?>

To output the file to a new filename, using previous example:

// Output
imagejpeg($rotate, "new-" . $filename);
?>
Jordan S. Jones
Yes. This will rotate an image. But how do I decide which images to rotate? I want a function which examines the image headers and checks whether it's been rotated by F-Spot. If it hasn't been, the function stops. If it has been, the function rotates the image by the appropriate amount.F-Spot (and some other programmes, including some cameras), rotates images by adding a note to say the image has been rotated. Other programmes ignore that note, and don't show the image rotated. I want to find images which contain that note and rotate them properly.
TRiG
A: 

In Perl, I think you want "exiftool -Orientation". The PHP equivalent seems to be accessible through "exif_read_data".

Dave McLaughlin
Now, why didn't I think of searching for "orientation"? That's brilliant. There's even some code shown in the php manual comments from mafo which I'll be able to use.http://php.net/manual/en/function.exif-read-data.php
TRiG
It's always easier to phrase the question when you know the answer!
Dave McLaughlin
I still haven't got it quite working, but I know where I'm going now. When I have some actual working code I'll post it here.
TRiG
A: 

This should be an edit to the question, but my accounts are messing me up, so this is a new account and I can't edit questions.

Here's the code I've written (stolen).

<?php

// overkill? definitely
define('MIRROR_HORIZONTAL', 1);
define('MIRROR_VERTICAL',   2);
define('MIRROR_BOTH',       3);
define('CR',          PHP_EOL); // carriage return

// memory: allow script to run for longer than usual time limit
set_time_limit(0);
ini_set('memory_limit', -1);
clearstatcache(); // there's no reason why this info should be cached anyway, but it does no harm to be sure

// plain and simple, baby; plain and simple
header('Content-Type: text/plain');

// find the brats
$c = array();
recursive_glob(getcwd(), $c);

function recursive_glob($path, &$c) {
    $a = glob($path . '/*');
    foreach ($a as $v) {
        if (is_dir($v)) {
            recursive_glob($v, $c);
        } else {
            $c[] = $v;
        }
    }
}

$u = count($c);
echo CR . 'Total number of files: ' . $u . CR . CR;

// uploaded from digital camera: a mixture of photos and videos (also, the recursive_glob() function above will find this file itself)
$i = 0; // count iterator: total images
$j = 0; // count iterator: total non-images
$k = 0; // count iterator: total images rotated
foreach ($c as $v) {
    $ext = substr($v, -3);
    if ($ext == 'jpg') { // I wanted to do this properly, by using finfo functions to check MIME types, but I couldn't get it to work and I don't actually need it
        $i++;
        echo clean_image_rotation($v, $k) . CR; // do the work
    } else {
        $j++;
        echo $v . ' - not JPEG' . CR; // filter out the videos
    }
}

echo CR . 'Image clean complete.' . CR . CR . 'Total files found: ' . $u . CR . 'Total JPEGs found: ' . $i . CR . 'Total images rotated: ' . $k . CR . CR . 'END';

function clean_image_rotation($file, &$k) {
    // http://stackoverflow.com/questions/1718847/use-php-or-perl-to-properly-rotate-jpeg-images
    // http://ie.php.net/manual/en/function.exif-read-data.php#76964
    // http://bytes.com/topic/php/answers/641481-thumbnail-orientation
    $exif  = exif_read_data($file, 'IFD0');
    if (!$exif) $exif = array();
    $ort   = isset($exif['IFD0']['Orientation']) ? $exif['IFD0']['Orientation'] : 1;

        switch($ort) {
        case 1:
            $do = 'nothing';
            break;

        case 2:
            $do = 'horizontal flip';
            flipImage($file, MIRROR_HORIZONTAL);
            $k++;
            break;

        case 3:
            $do = '180 rotate left';
            rotateImage($file, 180);
            $k++;
            break;

        case 4:
            $do = 'vertical flip';
            flipImage($file, MIRROR_VERTICAL);
            $k++;
            break;

        case 5:
            $do = 'vertical flip + 90 rotate right';
            flipImage($file, MIRROR_VERTICAL);
            rotateImage($file, -90);
            $k++;
            break;

        case 6:
            $do = '90 rotate right';
            rotateImage($file, -90);
            $k++;
            break;

        case 7:
            $do = 'horizontal flip + 90 rotate right';
            flipImage($file, MIRROR_HORIZONTAL);
            rotateImage($file, -90);
            $k++;
            break;

        case 8:
            $do = '90 rotate left';
            rotateImage($file, 90);
            $k++;
            break;
        }

        return $file . ' - ' . $do;
}

function flipImage($src, $dest, $type) {
    return;
    // http://php.net/manual/en/function.imagecopy.php#42803
    $imgsrc  = imagecreatefromjpeg($src);
    $width   = imagesx($imgsrc);
    $height  = imagesy($imgsrc);
    $imgdest = imagecreatetruecolor($width, $height);

    for ($x = 0; $x < $width; $x++) {
        for ($y = 0; $y < $height; $y++) {
            switch ($type) {
            case MIRROR_HORIZONTAL:
                imagecopy($imgdest, $imgsrc, $width - $x - 1, $y, $x, $y, 1, 1);
                break;
            case MIRROR_VERTICAL:
                imagecopy($imgdest, $imgsrc, $x, $height - $y - 1, $x, $y, 1, 1);
                break;
            case MIRROR_BOTH:
                imagecopy($imgdest, $imgsrc, $width - $x - 1, $height - $y - 1, $x, $y, 1, 1);
                break;
            default:
                return false;
            }
        }
    }

    imagejpeg($imgdest, $dest);

    imagedestroy($imgsrc);
    imagedestroy($imgdest);

    return true;
}

function rotateImage($src, $degrees) {
    return;
    return imagerotate($src, $degrees, 0);
}

As you can see, the images won't actually rotate in this version (both rotation functions simply return. This was for a test run, which should dump to screen what the real code would actually do. The output is a long list of filenames, and then this:

Image clean complete.

Total files found: 2046 Total JPEGs found: 1982 Total images rotated: 0

END

That's not what I was expecting. I know for a fact that some of these images have a sideways orientation, and this script should find them and fix them. Any idea what I'm doing wrong?

TRiG