blob: eb2b4612aef703e8bfea4ba6736d1e90467a94a0 [file] [log] [blame]
<?php
/**
* This file contains the ezcImageImagemagickBaseHandler 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 ImageConversion
* @version //autogentag//
* @copyright Copyright (C) 2005-2010 eZ Systems AS. All rights reserved.
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @filesource
*/
/**
* ezcImageHandler implementation for ImageMagick.
* This class only implements the base funtionality of handling images with
* ImageMagick. If you want to manipulate images using ImageMagick in your
* application, you should use the {@link ezcImageImagemagickHandler}.
*
* You can use this base class to implement your own filter set on basis of
* ImageMagick, but you can also use {@link ezcImageImagemagickHandler} for
* this and profit from its already implemented filters.
*
* @see ezcImageConverter
* @see ezcImageHandler
*
* @package ImageConversion
* @version //autogentag//
*/
class ezcImageImagemagickBaseHandler extends ezcImageMethodcallHandler
{
/**
* Path to the convert binary.
*
* @var string
*/
private $binary;
/**
* Map of MIME types to convert tags.
*
* @var array(string=>string)
*/
private $tagMap = array();
/**
* Filter options per reference.
*
* @var array(string=>array)
*/
private $filterOptions = array();
/**
* Composite image setting per reference.
*
* @var array(string=>bool)
*/
private $compositeImages = array();
/**
* Create a new image handler.
* Creates an image handler. This should never be done directly,
* but only through the manager for configuration reasons. One can
* get a direct reference through manager afterwards.
*
* This handler has an option 'binary' available, which allows you to
* explicitly set the path to your ImageMagicks "convert" binary (this
* may be necessary on Windows, since there may be an obscure "convert.exe"
* in the $PATH variable available, which has nothing to do with
* ImageMagick).
*
* @throws ezcImageHandlerNotAvailableException
* If the ImageMagick binary is not found.
*
* @param ezcImageHandlerSettings $settings Settings for the handler.
*/
public function __construct( ezcImageHandlerSettings $settings )
{
// Check for ImageMagick
$this->checkImageMagick( $settings );
$this->determineTypes();
parent::__construct( $settings );
}
/**
* Load an image file.
* Loads an image file and returns a reference to it.
*
* @param string $file File to load.
* @param string $mime The MIME type of the file.
*
* @return string Reference to the file in this handler.
*
* @see ezcImageAnalyzer
*
* @throws ezcBaseFileNotFoundException
* If the desired file does not exist.
* @throws ezcImageMimeTypeUnsupportedException
* If the desired file has a not recognized type.
* @throws ezcImageFileNameInvalidException
* If an invalid character (", ', $) is found in the file name.
*/
public function load( $file, $mime = null )
{
$this->checkFileName( $file );
$ref = $this->loadCommon( $file, $mime );
// Atomic file operation
$fileTmp = tempnam( dirname( $file ) . DIRECTORY_SEPARATOR, '.' . basename( $file ) );
copy( $file, $fileTmp );
$this->setReferenceData( $ref, $fileTmp, 'resource' );
return $ref;
}
/**
* Save an image file.
* Saves a given open file. Can optionally save to a new file name.
*
* @see ezcImageHandler::load()
*
* @param string $image File reference created through load().
* @param string $newFile Filename to save the image to.
* @param string $mime New MIME type, if differs from initial one.
* @param ezcImageSaveOptions $options Save options.
* @return void
*
* @throws ezcBaseFilePermissionException
* If the desired file exists and is not writeable.
* @throws ezcImageMimeTypeUnsupportedException
* If the desired MIME type is not recognized.
* @throws ezcImageFileNameInvalidException
* If an invalid character (", ', $) is found in the file name.
*/
public function save( $image, $newFile = null, $mime = null, ezcImageSaveOptions $options = null )
{
if ( $options === null )
{
$options = new ezcImageSaveOptions();
}
if ( $newFile !== null )
{
$this->checkFileName( $newFile );
}
// Check is transparency must be converted
if ( $this->needsTransparencyConversion( $this->getReferenceData( $image, 'mime' ), $mime ) && $options->transparencyReplacementColor !== null )
{
$this->addFilterOption( $image, '-background', $this->colorArrayToString( $options->transparencyReplacementColor ) );
$this->addFilterOption( $image, '-flatten' );
}
$this->saveCommon( $image, $newFile, $mime );
switch ( $this->getReferenceData( $image, 'mime' ) )
{
case "image/jpeg":
if ( $options->quality !== null )
{
$this->addFilterOption( $image, "-quality", $options->quality );
}
break;
case "image/png":
if ( $options->compression !== null )
{
// ImageMagick uses qualtiy options here and incorporates filter options
$this->addFilterOption( $image, "-quality", $options->compression * 10 );
}
break;
}
// Prepare ImageMagick command
// Here we need a work around, because older ImageMagick versions do not
// support this option order
if ( isset( $this->compositeImages[$image] ) )
{
$command = $this->binary . ' ' .
( isset( $this->filterOptions[$image] ) ? implode( ' ', $this->filterOptions[$image] ) : '' ) . ' ' .
escapeshellarg( $this->getReferenceData( $image, 'resource' ) ) . ' ' .
implode( ' ', $this->compositeImages[$image] ) . ' ' .
escapeshellarg( $this->tagMap[$this->getReferenceData( $image, 'mime' )] . ':' . $this->getReferenceData( $image, 'resource' ) );
}
else
{
$command = $this->binary . ' ' .
escapeshellarg( $this->getReferenceData( $image, 'resource' ) ) . ' ' .
( isset( $this->filterOptions[$image] ) ? implode( ' ', $this->filterOptions[$image] ) : '' ) . ' ' .
escapeshellarg( $this->tagMap[$this->getReferenceData( $image, 'mime' )] . ':' . $this->getReferenceData( $image, 'resource' ) );
}
// Prepare to run ImageMagick command
$descriptors = array(
array( 'pipe', 'r' ),
array( 'pipe', 'w' ),
array( 'pipe', 'w' ),
);
// Open ImageMagick process
$imageProcess = proc_open( $command, $descriptors, $pipes );
// Close STDIN pipe
fclose( $pipes[0] );
$errorString = '';
$outputString = '';
// Read STDERR
do
{
$outputString .= rtrim( fgets( $pipes[1], 1024 ), "\n" );
$errorString .= rtrim( fgets( $pipes[2], 1024 ), "\n" );
} while ( !feof( $pipes[2] ) );
// Wait for process to terminate and store return value
$status = proc_get_status( $imageProcess );
while ( $status['running'] !== false )
{
// Sleep 1/100 second to wait for convert to exit
usleep( 10000 );
$status = proc_get_status( $imageProcess );
}
$return = proc_close( $imageProcess );
// Process potential errors
// Exit code may be messed up with -1, especially on Windoze
if ( ( $status['exitcode'] != 0 && $status['exitcode'] != -1 ) || strlen( $errorString ) > 0 )
{
throw new ezcImageFileNotProcessableException(
$this->getReferenceData( $image, 'resource' ),
"The command '{$command}' resulted in an error ({$status['exitcode']}): '{$errorString}'. Output: '{$outputString}'"
);
}
// Finish atomic file operation
copy( $this->getReferenceData( $image, 'resource' ), $this->getReferenceData( $image, 'file' ) );
}
/**
* Returns a string representation of the given color array.
*
* ImageConversion uses arrays to represent color values, in the format:
* <code>
* array(
* 255,
* 0,
* 0,
* )
* </code>
* This array represents the color red.
*
* This method takes such a color array and converts it into a string
* representation usable by the convert binary. For the above examle it
* would be '#FF0000'.
*
* @param array $color
* @return void
*
* @throws ezcBaseValueException
* if one of the color values in the array is invalid (not integer,
* smaller than 0 or larger than 255).
*/
protected function colorArrayToString( array $color )
{
$colorString = '#';
$i = 0;
foreach ( $color as $id => $colorVal )
{
if ( $i++ > 2 )
{
break;
}
if ( !is_int( $colorVal ) || $colorVal < 0 || $colorVal > 255 )
{
throw new ezcBaseValueException( "color[$id]", $color[$id], 'int > 0 and < 256' );
}
$colorString .= sprintf( '%02x', $colorVal );
}
return $colorString;
}
/**
* Close the file referenced by $image.
* Frees the image reference. You should call close() before.
*
* @see ezcImageHandler::load()
* @see ezcImageHandler::save()
* @param string $image The image reference.
*/
public function close( $image )
{
unlink( $this->getReferenceData( $image, 'resource' ) );
$this->setReferenceData( $image, false, 'resource' );
$this->closeCommon( $image );
}
/**
* Add a filter option to a given reference
*
* @param string $reference The reference to add a filter for.
* @param string $name The option name.
* @param string $parameter The option parameter.
* @return void
*/
protected function addFilterOption( $reference, $name, $parameter = null )
{
$this->filterOptions[$reference][] = $name . ( $parameter !== null ? ' ' . escapeshellarg( $parameter ) : '' );
}
/**
* Add an image to composite with the given reference.
*
* @param string $reference The reference to add an image to
* @param string $file The file to composite with the image.
* @return void
*/
protected function addCompositeImage( $reference, $file )
{
$this->compositeImages[$reference][] = $file;
}
/**
* Determines the supported input/output types supported by handler.
* Set's various attributes to reflect the MIME types this handler is
* capable to process.
*
* @return void
*
* @apichange Faulty MIME type "image/svg" will be removed and replaced by
* correct MIME type image/svg+xml.
*/
private function determineTypes()
{
$tagMap = array(
'application/pcl' => 'PCL',
'application/pdf' => 'PDF',
'application/postscript' => 'PS',
'application/vnd.palm' => 'PDB',
'application/x-icb' => 'ICB',
'application/x-mif' => 'MIFF',
'image/bmp' => 'BMP3',
'image/dcx' => 'DCX',
'image/g3fax' => 'G3',
'image/gif' => 'GIF',
'image/jng' => 'JNG',
'image/jpeg' => 'JPG',
'image/pbm' => 'PBM',
'image/pcd' => 'PCD',
'image/pict' => 'PCT',
'image/pjpeg' => 'PJPEG',
'image/png' => 'PNG',
'image/ras' => 'RAS',
'image/sgi' => 'SGI',
'image/svg+xml' => 'SVG',
// Left over for BC reasons
'image/svg' => 'SVG',
'image/tga' => 'TGA',
'image/tiff' => 'TIF',
'image/vda' => 'VDA',
'image/vnd.wap.wbmp' => 'WBMP',
'image/vst' => 'VST',
'image/x-fits' => 'FITS',
'image/x-otb' => 'OTB',
'image/x-palm' => 'PALM',
'image/x-pcx' => 'PCX',
'image/x-pgm' => 'PGM',
'image/psd' => 'PSD',
'image/x-ppm' => 'PPM',
'image/x-ptiff' => 'PTIF',
'image/x-viff' => 'VIFF',
'image/x-xbitmap' => 'XPM',
'image/x-xv' => 'P7',
'image/xpm' => 'PICON',
'image/xwd' => 'XWD',
'text/plain' => 'TXT',
'video/mng' => 'MNG',
'video/mpeg' => 'MPEG',
'video/mpeg2' => 'M2V',
);
$types = array_keys( $tagMap );
$this->inputTypes = $types;
$this->outputTypes = $types;
$this->tagMap = $tagMap;
}
/**
* Checks for ImageMagick on the system.
*
* @param ezcImageHandlerSettings $settings The settings object of the current handler instance.
* @return void
*
* @throws ezcImageHandlerNotAvailableException
* If the ImageMagick binary is not found.
*/
private function checkImageMagick( ezcImageHandlerSettings $settings )
{
if ( !isset( $settings->options['binary'] ) )
{
$this->binary = ezcBaseFeatures::getImageConvertExecutable();
}
else if ( file_exists( $settings->options['binary'] ) )
{
$this->binary = $settings->options['binary'];
}
if ( $this->binary === null )
{
throw new ezcImageHandlerNotAvailableException(
'ezcImageImagemagickHandler',
'ImageMagick not installed or not available in PATH variable.'
);
}
// Prepare to run ImageMagick command
$descriptors = array(
array( 'pipe', 'r' ),
array( 'pipe', 'w' ),
array( 'pipe', 'w' ),
);
// Open ImageMagick process
$imageProcess = proc_open( $this->binary, $descriptors, $pipes );
// Close STDIN pipe
fclose( $pipes[0] );
$outputString = '';
// Read STDOUT
do
{
$outputString .= rtrim( fgets( $pipes[1], 1024 ), "\n" );
} while ( !feof( $pipes[1] ) );
$errorString = '';
// Read STDERR
do
{
$errorString .= rtrim( fgets( $pipes[2], 1024 ), "\n" );
} while ( !feof( $pipes[2] ) );
// Wait for process to terminate and store return value
$return = proc_close( $imageProcess );
// Process potential errors
if ( strlen( $errorString ) > 0 || strpos( $outputString, 'ImageMagick' ) === false )
{
throw new ezcImageHandlerNotAvailableException( 'ezcImageImagemagickHandler', 'ImageMagick not installed or not available in PATH variable.' );
}
}
/**
* Creates default settings for the handler and returns it.
* The reference name will be set to 'ImageMagick'.
*
* @return ezcImageHandlerSettings
*/
static public function defaultSettings()
{
return new ezcImageHandlerSettings( 'ImageMagick', 'ezcImageImagemagickHandler' );
}
}
?>