blob: 760037672c33150a6209760f1827db5eae32bd05 [file] [log] [blame]
<?php
/**
* File containing the ezcImageAnalyzerPhpHandler class.
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
* @package ImageAnalysis
* @version //autogentag//
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @filesource
*/
/**
* Class to retrieve information about a given image file.
* This handler implements image analyzation using direct PHP functionality,
* mainly the {@link getimagesize()} function and, if available, the EXIF
* extension {@link exif_read_data()}. The driver is capable of determining
* the type the following file formats:
*
* - GIF
* - JPG
* - PNG
* - SWF
* - SWC
* - PSD
* - TIFF
* - BMP
* - IFF
* - JP2
* - JPX
* - JB2
* - JPC
* - XBM
* - WBMP
*
* The driver determines the MIME type of these images (using the
* {@link ezcImageAnalyzerPhpHandler::analyzeType()} method). The width,
* height and size of the given image are always available after analyzing a
* file, if the file in general can be analyzed
* {@link ezcImageAnalyzerPhpHandler::canAnalyze()}.
*
* For JPEG and TIFF images this driver will try to read in information using
* the EXIF extension in PHP and fills in the following properties of the
* {@link ezcImageAnalyzerData} struct, returned by the
* {@link ezcImageAnalyzerPhpHandler::analyzeImage()} method:
* - exif
* - isColor
* - comment
* - copyright
* - date
* - hasThumbnail
* - isAnimated
*
* For GIF (also animated) it finds information by scanning the file manually
* and fills in the following properties of the
* {@link ezcImageAnalyzerData} struct, returned by the
* {@link ezcImageAnalyzerPhpHandler::analyzeImage()} method:
* - mode
* - transparencyType
* - comment
* - commentList
* - colorCount
* - isAnimated
*
* @package ImageAnalysis
* @version //autogentag//
*/
class ezcImageAnalyzerPhpHandler extends ezcImageAnalyzerHandler
{
/**
* Analyzes the image type.
*
* This method analyzes image data to determine the MIME type. This method
* returns the MIME type of the file to analyze in lowercase letters (e.g.
* "image/jpeg") or false, if the images MIME type could not be determined.
*
* For a list of image types this handler will be able to analyze, see
* {@link ezcImageAnalyzerPhpHandler}.
*
* @param string $file The file to analyze.
* @return string|bool The MIME type if analyzation suceeded or false.
*/
public function analyzeType( $file )
{
$data = getimagesize( $file );
if ( $data === false )
{
return false;
}
return image_type_to_mime_type( $data[2] );
}
/**
* Analyze the image for detailed information.
*
* This may return various information about the image, depending on it's
* type. All information is collected in the struct
* {@link ezcImageAnalyzerData}. At least the
* {@link ezcImageAnalyzerData::$mime} attribute is always available, if the
* image type can be analyzed at all. Additionally this handler will always
* set the {@link ezcImageAnalyzerData::$width},
* {@link ezcImageAnalyzerData::$height} and
* {@link ezcImageAnalyzerData::$size} attributes. For detailes information
* on the additional data returned, see {@link ezcImageAnalyzerPhpHandler}.
*
* @throws ezcImageAnalyzerFileNotProcessableException
* If image file can not be processed.
* @param string $file The file to analyze.
* @return ezcImageAnalyzerData
*/
public function analyzeImage( $file )
{
$data = getimagesize( $file );
if ( $data === false )
{
throw new ezcImageAnalyzerFileNotProcessableException(
$file,
'getimagesize() returned false.'
);
}
$dataStruct = new ezcImageAnalyzerData();
$dataStruct->width = $data[0];
$dataStruct->height = $data[1];
$dataStruct->mime = image_type_to_mime_type( $data[2] );
$dataStruct->size = filesize( $file );
if ( ( $dataStruct->mime === 'image/jpeg' || $dataStruct->mime === 'image/tiff' )
&& ezcBaseFeatures::hasFunction( 'exif_read_data')
)
{
$this->analyzeExif( $file, $dataStruct );
}
elseif ( $dataStruct->mime === 'image/gif' )
{
$this->analyzeGif( $file, $dataStruct );
}
return $dataStruct;
}
/**
* Returns if the handler can analyze a given MIME type.
*
* This method returns if the driver is capable of analyzing a given MIME
* type. This method should be called before trying to actually analyze an
* image using the drivers {@link self::analyzeImage()} method.
*
* @param string $mime The MIME type to check for.
* @return bool True if the handler is able to analyze the MIME type.
*/
public function canAnalyze( $mime )
{
switch ( $mime )
{
case 'image/gif':
case 'image/jpeg':
case 'image/png':
case 'image/psd':
case 'image/bmp':
case 'image/tiff':
case 'image/tiff':
case 'image/jp2':
case 'application/x-shockwave-flash':
case 'image/iff':
case 'image/vnd.wap.wbmp':
case 'image/xbm':
return true;
}
return false;
}
/**
* Checks wether the GD handler is available on the system.
*
* Returns if PHP's {@link getimagesize()} function is available.
*
* @return bool True is the handler is available.
*/
public function isAvailable()
{
return ezcBaseFeatures::hasFunction( 'getimagesize' );
}
/**
* Analyze EXIF enabled file format for EXIF data entries.
*
* The image file is analyzed by calling exif_read_data and placing the
* result in self::exif. In addition it fills in extra properties from
* the EXIF data for easy and uniform access.
*
* @param string $file The file to analyze.
* @param ezcImageAnalyzerData $dataStruct The data struct to fill.
* @return ezcImageAnalyzerData The filled data struct.
*/
private function analyzeExif( $file, ezcImageAnalyzerData $dataStruct )
{
$dataStruct->exif = exif_read_data( $file, "COMPUTED,FILE", true, false );
// Section "COMPUTED"
if ( isset( $dataStruct->exif['COMPUTED']['Width'] ) && isset( $dataStruct->exif['COMPUTED']['Height'] ) )
{
$dataStruct->width = $dataStruct->exif['COMPUTED']['Width'];
$dataStruct->height = $dataStruct->exif['COMPUTED']['Height'];
}
if ( isset( $dataStruct->exif['COMPUTED']['IsColor'] ) )
{
$dataStruct->isColor = $dataStruct->exif['COMPUTED']['IsColor'] == 1;
}
if ( isset( $dataStruct->exif['COMPUTED']['UserComment'] ) )
{
$dataStruct->comment = $dataStruct->exif['COMPUTED']['UserComment'];
$dataStruct->commentList = array( $dataStruct->comment );
}
if ( isset( $dataStruct->exif['COMPUTED']['Copyright'] ) )
{
$dataStruct->copyright = $dataStruct->exif['COMPUTED']['Copyright'];
}
// Section THUMBNAIL
$dataStruct->hasThumbnail = isset( $dataStruct->exif['THUMBNAIL'] );
// Section "FILE"
if ( isset( $dataStruct->exif['FILE']['FileSize'] ) )
{
$dataStruct->size = $dataStruct->exif['FILE']['FileSize'];
}
if ( isset( $dataStruct->exif['FILE']['FileDateTime'] ) )
{
$dataStruct->date = $dataStruct->exif['FILE']['FileDateTime'];
}
// EXIF based image are never animated.
$dataStruct->isAnimated = false;
return $dataStruct;
}
/**
* Analyze GIF files for detailed information.
*
* The GIF file is analyzed by scanning for frame entries, if more than one
* is found it is assumed to be animated.
* It also extracts other information such as image width and height, color
* count, image mode, transparency type and comments.
*
* @throws ezcBaseFileIoException
* If image file could not be read.
* @throws ezcImageAnalyzerFileNotProcessableException
* If image file can not be processed.
* @param string $file The file to analyze.
* @param ezcImageAnalyzerData $dataStruct The data struct to fill.
* @return ezcImageAnalyzerData The filled data struct.
*/
private function analyzeGif( $file, ezcImageAnalyzerData $dataStruct )
{
if ( ( $fp = fopen( $file, 'rb' ) ) === false )
{
throw new ezcBaseFileIoException( $file, ezcBaseFileException::READ );
}
// Read GIF header
$magic = fread( $fp, 6 );
$offset = 6;
if ( $magic != 'GIF87a' &&
$magic != 'GIF89a' )
{
throw new ezcImageAnalyzerFileNotProcessableException( $file, 'Not a valid GIF image file' );
}
$info = array();
$version = substr( $magic, 3 );
$frames = 0;
// Gifs are always indexed
$dataStruct->mode = self::MODE_INDEXED;
$dataStruct->commentList = array();
$dataStruct->transparencyType = self::TRANSPARENCY_OPAQUE;
// Read Logical Screen Descriptor
$data = unpack( "v1width/v1height/C1bitfield/C1index/C1ration", fread( $fp, 7 ) );
$offset += 7;
$lsdFields = $data['bitfield'];
$globalColorCount = 0;
$globalColorTableSize = 0;
if ( $lsdFields >> 7 )
{
// Extract 3 bits for color count
$globalColorCount = ( 1 << ( ( $lsdFields & 0x07 ) + 1) );
// Each color entry is RGB ie. 3 bytes
$globalColorTableSize = $globalColorCount * 3;
}
$dataStruct->colorCount = $globalColorCount;
$dataStruct->width = $data['width'];
$dataStruct->height = $data['height'];
if ( $globalColorTableSize )
{
// Skip the color table, we don't need the data
fseek( $fp, $globalColorTableSize, SEEK_CUR );
$offset += $globalColorTableSize;
}
$done = false;
// Iterate over all blocks and extract information
while ( !$done )
{
$data = fread( $fp, 1 );
$offset += 1;
$blockType = ord( $data[0] );
if ( $blockType == 0x21 ) // Extension Introducer
{
$data .= fread( $fp, 1 );
$offset += 1;
$extensionLabel = ord( $data[1] );
if ( $extensionLabel == 0xf9 ) // Graphical Control Extension
{
$data = unpack( "C1blocksize/C1bitfield/v1delay/C1index/C1term", fread( $fp, 5 + 1 ) );
$gceFlags = $data['bitfield'];//ord( $data[1] );
// $animationTimer is currently not in use.
/* $animationTimer = $data['delay']; */
// Check bit 0
if ( $gceFlags & 0x01 )
{
$dataStruct->transparencyType = self::TRANSPARENCY_TRANSPARENT;
}
$offset += 5 + 1;
}
else if ( $extensionLabel == 0xff ) // Application Extension
{
$data = fread( $fp, 12 );
$offset += 12;
$dataBlockDone = false;
while ( !$dataBlockDone )
{
$data = unpack( "C1blocksize", fread( $fp, 1 ) );
$offset += 1;
$blockBytes = $data['blocksize'];
if ( $blockBytes )
{
// Skip application data, we don't need the data
fseek( $fp, $blockBytes, SEEK_CUR );
$offset += $blockBytes;
}
else
{
$dataBlockDone = true;
}
}
}
else if ( $extensionLabel == 0xfe ) // Comment Extension
{
$commentBlockDone = false;
$comment = false;
while ( !$commentBlockDone )
{
$data = unpack( "C1blocksize", fread( $fp, 1 ) );
$offset += 1;
$blockBytes = $data['blocksize'];
if ( $blockBytes )
{
// Append current block to comment
$data = fread( $fp, $blockBytes );
$comment .= $data;
$offset += $blockBytes;
}
else
{
$commentBlockDone = true;
}
}
if ( $comment )
{
if ( $dataStruct->comment === null )
{
$dataStruct->comment = $comment;
}
$dataStruct->commentList[] = $comment;
}
}
else
{
throw new ezcImageAnalyzerFileNotProcessableException( $file, "Invalid extension label 0x" . hexdec( $extensionLabel ) . " in GIF image." );
}
}
else if ( $blockType == 0x2c ) // Image Descriptor
{
++$frames;
$data .= fread( $fp, 9 );
$data = unpack( "C1separator/v1leftpos/v1toppos/v1width/v1height/C1bitfield", $data );
$localColorTableSize = 0;
$localColorCount = 0;
$idFields = $data['bitfield'];
if ( $idFields >> 7 ) // Local Color Table
{
// Extract 3 bits for color count
$localColorCount = ( 1 << ( ( $idFields & 0x07 ) + 1) );
// Each color entry is RGB ie. 3 bytes
$localColorTableSize = $localColorCount * 3;
}
if ( $localColorCount > $globalColorCount )
{
$dataStruct->colorCount = $localColorCount;
}
if ( $localColorTableSize )
{
// Skip the color table, we don't need the data
fseek( $fp, $localColorTableSize, SEEK_CUR );
$offset += $localColorTableSize;
}
$lzwCodeSize = fread( $fp, 1 ); // LZW Minimum Code Size, currently unused
$offset += 1;
$dataBlockDone = false;
while ( !$dataBlockDone )
{
$data = unpack( "C1blocksize", fread( $fp, 1 ) );
$offset += 1;
$blockBytes = $data['blocksize'];
if ( $blockBytes )
{
// Skip image data, we don't need the data
fseek( $fp, $blockBytes, SEEK_CUR );
$offset += $blockBytes;
}
else
{
$dataBlockDone = true;
}
}
}
else if ( $blockType == 0x3b ) // Trailer, end of stream
{
$done = true;
}
else
{
throw new ezcImageAnalyzerFileNotProcessableException( $file, "Invalid block type 0x" . hexdec( $blockType ) . " in GIF image." );
}
if ( feof( $fp ) )
{
break;
}
}
$dataStruct->isAnimated = $frames > 1;
return $dataStruct;
}
}
?>