diff --git a/lib/Type/TypeJpeg.php b/lib/Type/TypeJpeg.php index 9a6e40a..b337d13 100644 --- a/lib/Type/TypeJpeg.php +++ b/lib/Type/TypeJpeg.php @@ -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"; @@ -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 @@ -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) @@ -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(); + } } diff --git a/lib/Type/TypeTif.php b/lib/Type/TypeTif.php index af943e2..ba8bc5b 100644 --- a/lib/Type/TypeTif.php +++ b/lib/Type/TypeTif.php @@ -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; @@ -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} @@ -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) { diff --git a/tests/FastImageSize.php b/tests/FastImageSize.php index 1974a42..9e5d9b4 100644 --- a/tests/FastImageSize.php +++ b/tests/FastImageSize.php @@ -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)), @@ -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 - ); } diff --git a/tests/fixture/dog.jpg b/tests/fixture/dog.jpg new file mode 100644 index 0000000..c5cf855 Binary files /dev/null and b/tests/fixture/dog.jpg differ diff --git a/tests/fixture/phpBB_logo.jpg b/tests/fixture/phpBB_logo.jpg new file mode 100755 index 0000000..653e180 Binary files /dev/null and b/tests/fixture/phpBB_logo.jpg differ