Skip to content

Commit

Permalink
Merge pull request #35 from marc1706/ticket/34
Browse files Browse the repository at this point in the history
Retrieve image size info from EXIF IFD if possible
  • Loading branch information
marc1706 authored Mar 26, 2017
2 parents 02ab1b8 + f15980e commit 5f7e837
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 5 deletions.
101 changes: 100 additions & 1 deletion lib/Type/TypeJpeg.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ class TypeJpeg extends TypeBase
/** @var string JPEG header */
const JPEG_HEADER = "\xFF\xD8";

/** @var string JPEG EXIF header */
const JPEG_EXIF_HEADER = "\x45\x78\x69\x66\x00\x00";

/** @var string Start of frame marker */
const SOF_START_MARKER = "\xFF";

Expand Down Expand Up @@ -143,6 +146,13 @@ protected function getSizeInfo()
break;
}

$size = $this->checkExifData($i);

if (count($size) > 0)
{
break;
}

if ($this->isSofMarker($this->data[$i], $this->data[$i + 1]))
{
// Extract size info from SOF marker
Expand Down Expand Up @@ -181,7 +191,10 @@ protected function checkForAppMarker($dataLength, &$index)
$this->setApp1Flags($this->data[$index + 1]);

// Skip over length of APP header
$index += (int) $length;
if (!$this->isApp1Marker($this->data[$index], $this->data[$index + 1]))
{
$index += (int)$length;
}

// Make sure we don't exceed the data length
if ($index >= $dataLength)
Expand Down Expand Up @@ -209,4 +222,90 @@ protected function setApp1Flags($data)
$this->foundXmp = $data === $this->appMarkers[1];
}
}

/**
* Check EXIF data for valid image dimensions
*
* @param int $index Current search index
*
* @return array Array with size info or empty array if no size info was found
*/
protected function checkExifData(&$index)
{
if ($this->foundExif && substr($this->data, $index + self::SHORT_SIZE, self::LONG_SIZE + self::SHORT_SIZE) == self::JPEG_EXIF_HEADER)
{
$typeTif = new TypeTif($this->fastImageSize);

$data = substr($this->data, $index + self::SHORT_SIZE * 2 + self::LONG_SIZE);

$signature = substr($data, 0, self::SHORT_SIZE);

$typeTif->setByteType($signature);

$size = array();

// Get offset of IFD
list(, $offset) = unpack($typeTif->typeLong, substr($data, self::LONG_SIZE, self::LONG_SIZE));

// Get size of IFD
list(, $sizeIfd) = unpack($typeTif->typeShort, substr($data, $offset, self::SHORT_SIZE));

// Skip 2 bytes that define the IFD size
$offset += self::SHORT_SIZE;

// Filter through IFD
for ($i = 0; $i <= $sizeIfd; $i++)
{
// Get IFD tag
$type = unpack($typeTif->typeShort, substr($data, $offset, self::SHORT_SIZE));

// Get field type of tag
$fieldType = unpack($typeTif->typeShort . 'type', substr($data, $offset + self::SHORT_SIZE, self::SHORT_SIZE));

// Get IFD entry
$ifdValue = substr($data, $offset + 2 * self::LONG_SIZE, self::LONG_SIZE);

// Set size of field
$fieldSize = $fieldType['type'] === TypeTif::TIF_TAG_TYPE_SHORT ? $typeTif->typeShort : $typeTif->typeLong;

if ($type[1] === TypeTif::TIF_TAG_EXIF_IMAGE_WIDTH)
{
$size = array_merge($size, (unpack($fieldSize . 'width', $ifdValue)));
}
else if ($type[1] === TypeTif::TIF_TAG_EXIF_IMAGE_HEIGHT)
{
$size = array_merge($size, (unpack($fieldSize . 'height', $ifdValue)));
}

// See if we can find the EXIF IFD data offset
if ($type[1] === TypeTif::TIF_TAG_EXIF_OFFSET)
{
list(, $newOffset) = unpack($fieldSize, $ifdValue);
list(, $newSizeIfd) = unpack($typeTif->typeShort, substr($data, $newOffset, self::SHORT_SIZE));
$i = 0;
$sizeIfd = $newSizeIfd;
$offset = $newOffset + self::SHORT_SIZE;
continue;
}

$offset += TypeTif::TIF_IFD_ENTRY_SIZE;
}

if (count($size) > 0 && !empty($size['width']) && !empty($size['height']))
{
return $size;
}
else if ($index >= 2 && $this->isApp1Marker($this->data[$index - 2], $this->data[$index - 1]))
{
// Extract length from APP marker and skip over it if it didn't
// contain the actual image dimensions
list(, $unpacked) = unpack("H*", substr($this->data, $index, 2));

$length = hexdec(substr($unpacked, 0, 4));
$index += $length - self::SHORT_SIZE;
}
}

return array();
}
}
15 changes: 12 additions & 3 deletions lib/Type/TypeTif.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@ class TypeTif extends TypeBase
/** @var int TIF tag for image width */
const TIF_TAG_IMAGE_WIDTH = 256;

/** @var int TIF tag for exif IFD offset */
const TIF_TAG_EXIF_OFFSET = 34665;

/** @var int TIF tag for Image X resolution in pixels */
const TIF_TAG_EXIF_IMAGE_WIDTH = 0xA002;

/** @var int TIF tag for Image Y resolution in pixels */
const TIF_TAG_EXIF_IMAGE_HEIGHT = 0xA003;

/** @var int TIF tag type for short */
const TIF_TAG_TYPE_SHORT = 3;

Expand All @@ -39,10 +48,10 @@ class TypeTif extends TypeBase
protected $size;

/** @var string Bit type of long field */
protected $typeLong;
public $typeLong;

/** @var string Bit type of short field */
protected $typeShort;
public $typeShort;

/**
* {@inheritdoc}
Expand Down Expand Up @@ -99,7 +108,7 @@ public function getSize($filename)
*
* @param string $signature Header signature
*/
protected function setByteType($signature)
public function setByteType($signature)
{
if ($signature === self::TIF_SIGNATURE_INTEL)
{
Expand Down
4 changes: 3 additions & 1 deletion tests/FastImageSize.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ public function dataGetImageSize()
array('meh', '', false),
array('meh', 'image/meh', false),
array('exif.jpg', 'image/jpeg', array('width' => 100, 'height' => 100, 'type' => IMAGETYPE_JPEG)),
array('phpBB_logo.jpg', '', array('width' => 152, 'height' => 53, 'type' => IMAGETYPE_JPEG)),
array('phpBB_logo.jpg', 'image/jpg', array('width' => 152, 'height' => 53, 'type' => IMAGETYPE_JPEG)),
array('dog.jpg', '', array('width' => 300, 'height' => 300, 'type' => IMAGETYPE_JPEG)),
// Capital file names
array('JPGL', 'image/jpg', array('width' => 1, 'height' => 1, 'type' => IMAGETYPE_JPEG)),
array('JPGL', '', array('width' => 1, 'height' => 1, 'type' => IMAGETYPE_JPEG)),
Expand All @@ -99,7 +102,6 @@ public function dataGetImageSize()
array('png.PNG', 'image/png', array('width' => 1, 'height' => 1, 'type' => IMAGETYPE_PNG)),
array('png.PNG', '', array('width' => 1, 'height' => 1, 'type' => IMAGETYPE_PNG)),
array('jpg.JPG', 'image/png', array('width' => 1, 'height' => 1, 'type' => IMAGETYPE_JPEG)), // extension override incorrect mime type

);
}

Expand Down
Binary file added tests/fixture/dog.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tests/fixture/phpBB_logo.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 5f7e837

Please sign in to comment.