Skip to content

Commit

Permalink
Merge pull request #10 from rarila/optimize-exif-handling
Browse files Browse the repository at this point in the history
Optimize exif orientation handling + add testing
  • Loading branch information
bobdenotter committed Jan 7, 2015
2 parents db6aa82 + f389d37 commit 0d8cf77
Show file tree
Hide file tree
Showing 14 changed files with 127 additions and 111 deletions.
13 changes: 8 additions & 5 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,26 @@
"email": "[email protected]"
}
],

"minimum-stability": "dev",

"require": {
"symfony/http-foundation": "~2.5",
"ext-gd": "*"
},



"require-dev": {
"bolt/bolt": "~2.0"
},

"autoload": {
"psr-4": {
"Bolt\\Thumbs\\": "src",
"Bolt\\Thumbs\\Tests\\": "tests"
}
},

"extra": {
"branch-alias": { "dev-master": "2.1-dev" }
}

}
2 changes: 1 addition & 1 deletion phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
verbose="false">

<testsuites>
<testsuite name="Bolt Thumnail Handler Test Suite">
<testsuite name="Bolt Thumbnail Handler Test Suite">
<directory>tests/</directory>
</testsuite>
</testsuites>
Expand Down
118 changes: 46 additions & 72 deletions src/ThumbnailCreator.php
Original file line number Diff line number Diff line change
Expand Up @@ -174,9 +174,13 @@ protected function doResize($src, $width, $height, $crop = false, $fit = false,
break;
case 'jpg':
$img = imagecreatefromjpeg($src);
// Handle exif orientation
$exif = $this->exifOrientation ? exif_read_data($src) : false;
if ($exif !== false && isset($exif['Orientation'])) {
$img = self::imageSetOrientationFromExif($img, $exif['Orientation']);
$modes = array(2 => 'H-', 3 => '-T', 4 => 'V-', 5 => 'VL', 6 => '-L', 7 => 'HL', 8 => '-R');
$orientation = isset($exif['Orientation']) ? $exif['Orientation'] : 0;
if (isset($modes[$orientation])) {
$mode = $modes[$orientation];
$img = self::imageFlipRotate($img, $mode[0], $mode[1]);
$w = imagesx($img);
$h = imagesy($img);
}
Expand Down Expand Up @@ -291,88 +295,58 @@ protected function getOutput($imageContent, $type)
}

/**
* Sets orientation of image based on exif data
*
* @param $img image resource
* @param $orientation the exif image orientation
**/
public static function imageSetOrientationFromExif($img, $orientation)
{
switch ($orientation) {
case 2: // horizontal flip
$img = self::imageFlip($img, 1);
break;
case 3: // 180 rotate left
$img = imagerotate($img, 180, 0);
break;
case 4: // vertical flip
$img = self::imageFlip($img, 0);
break;
case 5: // vertical flip + 90 rotate right
$img = self::imageFlip($img, 0);
$img = imagerotate($img, -90, 0);
break;
case 6: // 90 rotate right
$img = imagerotate($img, -90, 0);
break;
case 7: // horizontal flip + 90 rotate right
$img = self::imageFlip($img, 1);
$img = imagerotate($img, -90, 0);
break;
case 8: // 90 rotate left
$img = imagerotate($img, 90, 0);
break;
}

return $img;
}

/**
* Image Flip
* Image flip and rotate
*
* Based on http://stackoverflow.com/a/10001884/1136593
* Thanks Jon Grant
*
* @param $imgsrc (image to flip)
* @param $mode (0 = vertical, 1 = horizontal, 2 = both) - defaults to vertical flip
* @param $img (image to flip and/or rotate)
* @param $mode ('V' = vertical, 'H' = horizontal, 'HV' = both)
* @param $angle ('L' = -90°, 'R' = +90°, 'T' = 180°)
*
*/
public static function imageFlip($imgsrc, $mode = 0)
public static function imageFlipRotate($img, $mode, $angle)
{
$width = imagesx($imgsrc);
$height = imagesy($imgsrc);
// Flip the image
if ($mode === 'V' || $mode === 'H' || $mode === 'HV') {
$width = imagesx($img);
$height = imagesy($img);

$srcX = 0;
$srcY = 0;
$srcWidth = $width;
$srcHeight = $height;

switch ($mode) {
case 'V': // Vertical
$srcY = $height - 1;
$srcHeight = -$height;
break;
case 'H': // Horizontal
$srcX = $width - 1;
$srcWidth = -$width;
break;
case 'HV': // Both
$srcX = $width - 1;
$srcY = $height - 1;
$srcWidth = -$width;
$srcHeight = -$height;
break;
}

$src_x = 0;
$src_y = 0;
$src_width = $width;
$src_height = $height;
$imgdest = imagecreatetruecolor($width, $height);

switch ( $mode )
{
default:
case '0': //vertical
$src_y = $height -1;
$src_height= -$height;
break;
case '1': //horizontal
$src_x = $width -1;
$src_width = -$width;
break;
case '2': //both
$src_x = $width -1;
$src_y = $height -1;
$src_width = -$width;
$src_height = -$height;
break;
if (imagecopyresampled($imgdest, $img, 0, 0, $srcX, $srcY, $width, $height, $srcWidth, $srcHeight)) {
$img = $imgdest;
}
}

$imgdest = imagecreatetruecolor ( $width, $height );

if (imagecopyresampled($imgdest, $imgsrc, 0, 0, $src_x, $src_y , $width, $height, $src_width, $src_height))
{
return $imgdest;
// Rotate the image
if ($angle === 'L' || $angle === 'R' || $angle === 'T') {
$rotate = array('L' => 270, 'R' => 90, 'T' => 180);
$img = imagerotate($img, $rotate[$angle], 0);
}

return $imgsrc;
return $img;
}
}
8 changes: 4 additions & 4 deletions src/ThumbnailResponder.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,12 @@ public function __construct(Application $app, Request $request, ResizeInterface
public function initialize()
{
if (null !== $this->app['config']->get('general/thumbnails/notfound_image')) {
$file = $this->app['resources']->getPath('app'). '/' .$this->app['config']->get('general/thumbnails/notfound_image');
$file = $this->app['resources']->getPath('app') . '/' . $this->app['config']->get('general/thumbnails/notfound_image');
$this->resizer->setDefaultSource(new File($file, false));
}

if (null !== $this->app['config']->get('general/thumbnails/error_image')) {
$file = $this->app['resources']->getPath('app'). '/' .$this->app['config']->get('general/thumbnails/error_image');
$file = $this->app['resources']->getPath('app') . '/' . $this->app['config']->get('general/thumbnails/error_image');
$this->resizer->setErrorSource(new File($file, false));
}

Expand Down Expand Up @@ -184,11 +184,11 @@ public function saveStatic($imageContent)
$path = urldecode($this->request->getPathInfo());
try {
$webroot = dirname($this->request->server->get('SCRIPT_FILENAME'));
$savePath = dirname($webroot.$path);
$savePath = dirname($webroot . $path);
if (!is_dir($savePath)) {
mkdir($savePath, 0777, true);
}
file_put_contents($webroot.$path, $imageContent);
file_put_contents($webroot . $path, $imageContent);
} catch (\Exception $e) {

}
Expand Down
68 changes: 50 additions & 18 deletions tests/ThumbnailCreatorTest.php
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
<?php
namespace Bolt\Thumbs\Tests;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\File\File;

use Bolt\Application;
use Bolt\Configuration\ResourceManager;

use Bolt\Thumbs\ThumbnailCreator;

class ThumbnailCreatorTest extends \PHPUnit_Framework_TestCase
Expand All @@ -23,7 +17,6 @@ public function setup()
$this->jpg = __DIR__ . '/images/generic-logo.jpg';
$this->gif = __DIR__ . '/images/generic-logo.gif';
$this->png = __DIR__ . '/images/generic-logo.png';
require_once __DIR__ . '/../vendor/bolt/bolt/app/lib.php';
}

public function testSetup()
Expand All @@ -41,23 +34,23 @@ public function testFallbacksForBadDimensions()
$creator = new ThumbnailCreator();
$creator->setSource($src);

$ok_width = 624;
$ok_height = 351;
$okWidth = 624;
$okHeight = 351;

$testcases = array(
array(),
array('width' => $ok_width, 'height' => -20),
array('width' => $ok_width),
array('height' => $ok_height),
array('width' => 'A', 'height' => $ok_height),
array('width' => 123.456, 'height' => $ok_height),
array('width' => $okWidth, 'height' => -20),
array('width' => $okWidth),
array('height' => $okHeight),
array('width' => 'A', 'height' => $okHeight),
array('width' => 123.456, 'height' => $okHeight),
array('width' => 'both', 'height' => 'wrong'),
);

foreach ($testcases as $parameters) {
$creator->verify($parameters);
$this->assertEquals($ok_width, $creator->targetWidth);
$this->assertEquals($ok_height, $creator->targetHeight);
$this->assertEquals($okWidth, $creator->targetWidth);
$this->assertEquals($okHeight, $creator->targetHeight);
}
}

Expand Down Expand Up @@ -171,8 +164,15 @@ public function testPortraitResize()
$result = $creator->resize(array('width' => 200, 'height' => 500));
$compare = __DIR__ . '/images/timthumbs/resize_sample2_200_500.jpg';
file_put_contents(__DIR__ . '/tmp/test.jpg', $result);

// Original compare image is with v80, v90 creates a 2 byte smaller image (perhaps only on windows?)
$correction = 0;
if (preg_match('%CREATOR: gd-jpeg v1\.0 \(using IJG JPEG v(\d+)\)%', $result, $pm) && $pm[1] == '90') {
$correction = 2;
}

$this->assertEquals(getimagesize($compare), getimagesize(__DIR__ . '/tmp/test.jpg'));
$this->assertEquals(filesize($compare), filesize(__DIR__ . '/tmp/test.jpg'));
$this->assertEquals(filesize($compare), filesize(__DIR__ . '/tmp/test.jpg') + $correction);
}

public function testPortraitFit()
Expand All @@ -199,12 +199,44 @@ public function testPortraitBorder()
$this->assertEquals(filesize($compare), filesize(__DIR__ . '/tmp/test.jpg'));
}

public function testExifOrientation()
{
$images = array(
'1-top-left',
'2-top-right',
'3-bottom-right',
'4-bottom-left',
'5-left-top',
'6-right-top',
'7-right-bottom',
'8-left-bottom',
);
$resize = array('width' => 200, 'height' => 100);

foreach ($images as $name) {
$path = __DIR__ . '/tmp/' . $name . '.jpg';
// Create test image
$creator = new ThumbnailCreator();
$creator->setSource(new File(__DIR__ . '/images/exif-orientation/' . $name . '.jpg'));
$result = $creator->resize($resize);
file_put_contents($path, $result);
// Read test image
$img = imagecreatefromjpeg($path);
$width = imagesx($img);
$height = imagesy($img);
$rgb = imagecolorsforindex($img, imagecolorat($img, 0, 0));
// Assert image size and red color (fuzzy) in the upper left corner
$this->assertEquals($resize['width'], $width, 'Wrong width!');
$this->assertEquals($resize['height'], $height, 'Wrong height!');
$this->assertTrue($rgb['red'] > 250 && $rgb['green'] < 5 && $rgb['blue'] < 5, 'Wrong orientation!');
}
}

public function tearDown()
{
$tmp = __DIR__ . '/tmp/test.jpg';
if (is_readable($tmp)) {
unlink($tmp);
}

}
}
Loading

0 comments on commit 0d8cf77

Please sign in to comment.