Autorotation of photos based on exif data done

pain_elemental

Joined: 2005-02-22
Posts: 2
Posted: Tue, 2005-02-22 17:49

My first task is done. Here is it:

on file modules/exif/modules.inc change the line 245 to:

$this->setCallbacks('loadItemDetails|getSiteAdminViews|registerEventListeners');

then add this member function:

    /**
     * Event handler for GalleryEntity::save event.
     * Rotate image based on exif data.
     * @author Michel Moreira <michel@michelmoreira.com.br>
     * @see GalleryEventListener::handleEvent
     */
    function handleEvent($event) {
    $entity = $event->getEntity();

    /* See if is a data item */
    if (!GalleryUtilities::isA($entity, 'GalleryDataItem')) {
        return array(GalleryStatus::success(), '');
    }

    /* And if is one JPEG */
    if ($entity->getMimeType() != 'image/jpeg' && $entity->getMimeType() != 'image/pjpeg') {
        return array(GalleryStatus::success(), '');
    }

    /* Then gimme the path info */
    list($ret, $path) = $entity->fetchPath();
    if ($ret->isError()) {
        return array($ret->wrap(__FILE__, __LINE__), null);
    }

    list ($ret, $exifData) = ExifHelper::getExifData($path, EXIF_DETAILED);
    if ($ret->isError()) {
        return array($ret->wrap(__FILE__, __LINE__), null);
    }
    GalleryCoreApi::relativeRequireOnce('modules/exif/lib/exifer/exif.inc');
    $rawExifData = read_exif_data_raw($path, true);

    $orientation = $rawExifData["IFD0"]["Orientation_Verbose"]["RawData"];
    if ($orientation) {
        list ($ret, $rotatekit) = GalleryCoreApi::getToolkitByOperation('image/jpeg', 'rotate');
        list ($ret, $flipkit)   = GalleryCoreApi::getToolkitByOperation('image/jpeg', 'flip');

        $degree = 0;
        $x = false;
        $y = false;
        switch ($orientation)
        {
            case 1: //No Rotation, No Flip
                 break;
            case 2: //No Rotation, Flipped Horizontally
                 $x = true;
                 break;
            case 3: //Rotated 180 degrees, No Flip
                 $degree = 180;
                 break;
            case 4: //No Rotation, Flipped Vertically
                 $y = true;
                 break;
            case 5: //Flipped Horizontally, Rotated 90 degrees counter clockwise
                 $x = true;
                 $degree = -90;
                 break;
            case 6: //No Flip, Rotated 90 degrees clockwise
                 $degree = 90;
                 break;
            case 7: //Flipped Horizontally, Rotated 90 degrees clockwise
                 $x = true;
                 $degree = 90;
                 break;
            case 8: //No Flip, Rotated 90 degrees counter clockwise
                 $degree = 90;
                 break;
        }
    }
    if (isset($flipkit)) {
        $flipkit->performOperation($entity->getMimeType(), 'flip', $path, $path, array(0 => $x, 1=> $y));
    }
    if (isset($rotatekit)) {
        $rotatekit->performOperation($entity->getMimeType(), 'rotate', $path, $path, array(0 => $degree));
    }
    return array(GalleryStatus::success(), '');
    }

on file modules/core/gd/classes/GdToolkit.class
add this case test on performOperation function (line 395)

    case 'flip':
        /*
         * PHP >= 3.0.6
         * $parameters[0]: flip horizontally
         * $parameters[1]: flip vertically
         */
        $x = $parameters[0];
        $y = $parameters[1];
        if ($x || $y) {
            list ($ret, $width, $height) = $this->_getImageDimensionsForResource($sourceRes);
            if ($ret->isError()) {
                return array($ret->wrap(__FILE__, __LINE__), null);
            }
            list($ret, $destRes) = $this->_getTrueColorImageRes($width, $height);
            if ($ret->isError()) {
                return array($ret->wrap(__FILE__, __LINE__), null);
            }
            if ($x) {
                for ($x = 0; $x < $width; $x++) {
                    $gd->ImageCopy($destRes, $sourceRes, $width - 1 - $x, 0, $x, 0, 1, $height);
                }
            }
            if ($y) {
                for ($y = 0; $y < $height; $y++) {
                    ImageCopy($destRes, $sourceRes, 0, $height - 1 - $y, 0, $y, $width, 1);
                }

            }
        }
        break;

Needs add flip support for others image libraries. Needs also the option to enable/disable it on file upload or admin panel, or both.

:P

 
alindeman
alindeman's picture

Joined: 2002-10-06
Posts: 8194
Posted: Tue, 2005-02-22 18:09

Just some quick, pedantic comments:

    $orientation = $rawExifData["IFD0"]["Orientation_Verbose"]["RawData"]; 

has the possibility to generate a PHP notice. I'd use isset() to check if the array key is defined before referencing it.

array(0 => $x, 1=> $y)

That can just be replaced with array($x, $y), which looks cleaner to me. Same with array(0 => $degree)

Anyway, cool beans :)

 
pain_elemental

Joined: 2005-02-22
Posts: 2
Posted: Tue, 2005-02-22 18:19

Here the new version:
referencing a non-existing index also raises a notice.

    /**
     * Event handler for GalleryEntity::save event.
     * Rotate image based on exif data.
     * @author Michel Moreira <michel@michelmoreira.com.br>
     * @see GalleryEventListener::handleEvent
     */
    function handleEvent($event) {
    $entity = $event->getEntity();

    /* See if is a data item */
    if (!GalleryUtilities::isA($entity, 'GalleryDataItem')) {
        return array(GalleryStatus::success(), '');
    }

    /* And if is one JPEG */
    if ($entity->getMimeType() != 'image/jpeg' && $entity->getMimeType() != 'image/pjpeg') {
        return array(GalleryStatus::success(), '');
    }

    /* Then gimme the path info */
    list($ret, $path) = $entity->fetchPath();
    if ($ret->isError()) {
        return array($ret->wrap(__FILE__, __LINE__), null);
    }

    list ($ret, $exifData) = ExifHelper::getExifData($path, EXIF_DETAILED);
    if ($ret->isError()) {
        return array($ret->wrap(__FILE__, __LINE__), null);
    }
    GalleryCoreApi::relativeRequireOnce('modules/exif/lib/exifer/exif.inc');
    $rawExifData = read_exif_data_raw($path, true);
    if (isset($rawExifData) &&
        array_key_exists("IFD0", $rawExifData) &&
        array_key_exists("Orientation_Verbose", $rawExifData["IFD0"]) &&
        array_key_exists("RawData", $rawExifData["IFD0"]["Orientation_Verbose"])) {

        $orientation = $rawExifData["IFD0"]["Orientation_Verbose"]["RawData"];

        list ($ret, $rotatekit) = GalleryCoreApi::getToolkitByOperation('image/jpeg', 'rotate');
        list ($ret, $flipkit)   = GalleryCoreApi::getToolkitByOperation('image/jpeg', 'flip');

        $degree = 0;
        $x = false;
        $y = false;

        switch ($orientation)
        {
            case 1: //No Rotation, No Flip
                 break;
            case 2: //No Rotation, Flipped Horizontally
                 $x = true;
                 break;
            case 3: //Rotated 180 degrees, No Flip
                 $degree = 180;
                 break;
            case 4: //No Rotation, Flipped Vertically
                 $y = true;
                 break;
            case 5: //Flipped Horizontally, Rotated 90 degrees counter clockwise
                 $x = true;
                 $degree = -90;
                 break;
            case 6: //No Flip, Rotated 90 degrees clockwise
                 $degree = 90;
                 break;
            case 7: //Flipped Horizontally, Rotated 90 degrees clockwise
                 $x = true;
                 $degree = 90;
                 break;
            case 8: //No Flip, Rotated 90 degrees counter clockwise
                 $degree = 90;
                 break;
        }
        if (isset($flipkit)) {
            $flipkit->performOperation($entity->getMimeType(), 'flip', $path, $path, array($x, $y));
        }
        if (isset($rotatekit)) {
            $rotatekit->performOperation($entity->getMimeType(), 'rotate', $path, $path, array($degree));
        }
    }

    return array(GalleryStatus::success(), '');
    }
 
BorgKing
BorgKing's picture

Joined: 2002-09-12
Posts: 313
Posted: Tue, 2005-02-22 22:54

Is this code getting into the EXIF module? IMHO it should really be part of the G2 distribution. One more thing that would be great to have is enabling/disabling this option when uploading files. Since some of my users have their images already rotated properly, and others don't.

 
mindless
mindless's picture

Joined: 2004-01-04
Posts: 8601
Posted: Tue, 2005-02-22 23:40

BorgKing, yes it is posted here for code review prior to adding to cvs.
I agree this feature should be optional! For me a site admin option would suffice.. BorgKing, you'd prefer an option at upload time?

 
bharat
bharat's picture

Joined: 2002-05-21
Posts: 7934
Posted: Wed, 2005-02-23 06:51

This looks like a fine start. There are some issues that we need to
figure out, though. It looks like this is modifying the raw original
image which is something that we seek to avoid whenever possible.
Instead of modifying the original, we should instead create a
preferred derivative with the correct toolkit command and remap any
derivatives as appropriate. Look at
modules/core/ItemEditRotateAndScale.inc for ane example. This will
let the user undo any potential damage caused by the toolkit. And
since we're not modfying the embedded exif data here, this will keep
the exif data in sync with the image so if you use the image as a
source for another addition it will be internally consistent and you
won't wind up rotating it twice.

  • Diffs are easier to review than inline code in the forums, because we can then
    see your indentation too (which is important).
  • Move the event handler out of module.inc -- this is something that will be used infrequently
    so we should avoid paying the cost of bytecode compilation if we can avoid it.
  • There should be a unit test for the new event handler in the exif module
    (as well as ones for the other toolkits)
  • Don't call read_exif_data_raw directly. If you need to get to a single value
    it's better to create a new API in ExifHelper specifically for this purpose
    (and provide a unit test for that, too of course)
  • This is safe and more compact:
     
       if (isset($rawExifData['IFD0']['Orientation_Verbose']['RawData'])) { 
           $orientation = ...; 
       } 
    
  • Don't forget to check your return values in your getToolkitByOperation calls (2 of them)
  • curly brace in your switch statement should not be on a separate line
  • use /* */ comment style instead of //
  • No lines > 100 chars (hard to tell, but it seems like some of them are)

[/]

 
BorgKing
BorgKing's picture

Joined: 2002-09-12
Posts: 313
Posted: Wed, 2005-02-23 10:58

mindless, yes I would prefer an option at upload time. Most of my users rotate the pictures before uploading them, but some do not, and for those it would be good to have this option. Somehow this just can't be an admin option only (just default) in Galleries with multiple users.

 
mindless
mindless's picture

Joined: 2004-01-04
Posts: 8601
Posted: Wed, 2005-02-23 13:53

i think its ok for handleEvent to be in module.inc, just move the bulk of the work to a helper class.. the code in module.inc can check if the event is applicable (your first couple ifs, and you need to add one for newly created (see useralbum)..
on 2nd thought, shouldn't this be an ItemAddOption instead of event handler?

 
bharat
bharat's picture

Joined: 2002-05-21
Posts: 7934
Posted: Thu, 2005-02-24 01:54

It actually probably does make more sense for this to be an ItemAddOption. It'll be easier to configure/control that way since all configuration can be done inline on the item-add page.

pain_elemental, take a look at modules/core/CreateThumbnailOption.inc for an example of how to create an ItemAddOption.

 
mindless
mindless's picture

Joined: 2004-01-04
Posts: 8601
Posted: Thu, 2005-02-24 02:32

(or the ItemAddOption already in the exif module.. you can just add to that)

 
etiennesky

Joined: 2005-09-28
Posts: 39
Posted: Sun, 2005-10-02 23:38

I made a hack using jhead and jpegtran with "jhead - autorot <file>'.

I think this is the best way to go because it is lossless and could apply to the original without damage.

I have made a quick hack in modules/core/classes/helpers/GalleryItemHelper_medium.class in the addItemToAlbum() method.

What do you think, should there be a graphics toolkit or simply an option in the exif module for jhead path and per-file option?

I had a hard time with the path to jpegtran, but here is the hack:

	$ret = $newItem->save();
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}

	/* hack autorotate jpeg */
	if ($mimeType == 'image/jpeg' || $mimeType == 'image/jpg' || $mimeType == 'image/pjpeg') {	
	   list($ret, $filePath) = $newItem->fetchPath();
	   if (!$ret->isError()) {
	      $cwd = getcwd();	 
	      chdir("/home/www/tourigny.ca/cgi-bin/");
	      $jhead_exe = "/home/www/tourigny.ca/cgi-bin/jhead-static";
	      $cmd = array($jhead_exe, "-autorot", $filePath);
	      $platform = $gallery->getPlatform();
	      list($ret, $output) = $platform->exec(array($cmd));
	      chdir($cwd);
	      if (!$ret) {
		 echo "ERROR with command ";print_r($cmd);echo " for removing orientation<br>";
		 echo "output was: ";print_r($output); echo "<br>";
	      }
	   }
	}

	$ret = GalleryCoreApi::addExistingItemToAlbum($newItem, $albumId, true);