Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Retrieve image size info from EXIF IFD if possible #35

Merged
merged 2 commits into from
Mar 26, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.