1110 lines
34 KiB
PHP
1110 lines
34 KiB
PHP
<?php
|
|
/**
|
|
* Copyright 2002-2017 Horde LLC (http://www.horde.org/)
|
|
*
|
|
* See the enclosed file LICENSE for license information (LGPL). If you
|
|
* did not receive this file, see http://www.horde.org/licenses/lgpl21.
|
|
*
|
|
* @author Chuck Hagenbuch <chuck@horde.org>
|
|
* @author Michael J. Rubinsky <mrubinsk@horde.org>
|
|
* @author Jan Schneider <jan@horde.org>
|
|
* @category Horde
|
|
* @license http://www.horde.org/licenses/lgpl21 LGPL-2.1
|
|
* @package Image
|
|
*/
|
|
|
|
/**
|
|
* This class implements the Horde_Image API for the PHP GD extension.
|
|
*
|
|
* @author Chuck Hagenbuch <chuck@horde.org>
|
|
* @author Michael J. Rubinsky <mrubinsk@horde.org>
|
|
* @author Jan Schneider <jan@horde.org>
|
|
* @category Horde
|
|
* @copyright 2002-2017 Horde LLC
|
|
* @license http://www.horde.org/licenses/lgpl21 LGPL-2.1
|
|
* @package Image
|
|
*
|
|
* @property-read resource $_im The underlaying image resource.
|
|
*/
|
|
class Horde_Image_Gd extends Horde_Image_Base
|
|
{
|
|
/**
|
|
* Allocated color resources.
|
|
*
|
|
* @var int[]
|
|
*/
|
|
protected $_colors = array();
|
|
|
|
/**
|
|
* Capabilites of this driver.
|
|
*
|
|
* @var string[]
|
|
*/
|
|
protected $_capabilities = array(
|
|
'canvas',
|
|
'circle',
|
|
'crop',
|
|
'dashedLine',
|
|
'flip',
|
|
'grayscale',
|
|
'line',
|
|
'mirror',
|
|
'polygon',
|
|
'polyline',
|
|
'rectangle',
|
|
'resize',
|
|
'rotate',
|
|
'roundedRectangle',
|
|
'sepia',
|
|
'text',
|
|
'yellowize',
|
|
);
|
|
|
|
/**
|
|
* GD image resource for the current image data.
|
|
*
|
|
* @var resource
|
|
*/
|
|
protected $_im;
|
|
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @see Horde_Image_Base::_construct
|
|
*/
|
|
public function __construct($params, $context = array())
|
|
{
|
|
parent::__construct($params, $context);
|
|
if (!empty($params['filename'])) {
|
|
$this->loadFile($params['filename']);
|
|
} elseif (!empty($params['data'])) {
|
|
$this->loadString($params['data']);
|
|
} elseif (!empty($params['width'])) {
|
|
$this->_im = $this->create($this->_width, $this->_height);
|
|
$this->call(
|
|
'imageFill',
|
|
array(
|
|
$this->_im,
|
|
0, 0,
|
|
$this->_allocateColor($this->_background)
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*/
|
|
public function __get($property)
|
|
{
|
|
switch ($property) {
|
|
case '_im':
|
|
return $this->_im;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Displays the current image.
|
|
*/
|
|
public function display()
|
|
{
|
|
$this->headers();
|
|
$this->call('image' . $this->_type, array($this->_im));
|
|
}
|
|
|
|
/**
|
|
* Returns the raw data for this image.
|
|
*
|
|
* @param boolean $convert Ignored for Gd driver.
|
|
* @param array $options Array of options:
|
|
* - stream: If true, return as a stream resource.
|
|
* DEFAULT: false.
|
|
*
|
|
* @return string The raw image data.
|
|
*/
|
|
public function raw($convert = false, $options = array())
|
|
{
|
|
if (!is_resource($this->_im)) {
|
|
return '';
|
|
}
|
|
|
|
ob_start();
|
|
call_user_func('image' . $this->_type, $this->_im);
|
|
|
|
if (empty($options['stream'])) {
|
|
return ob_get_clean();
|
|
}
|
|
|
|
$s = new Horde_Stream_Temp();
|
|
$s->add(ob_get_clean(), true);
|
|
|
|
return $s->stream;
|
|
}
|
|
|
|
/**
|
|
* Resets the image data to defaults.
|
|
*/
|
|
public function reset()
|
|
{
|
|
parent::reset();
|
|
if (is_resource($this->_im)) {
|
|
$this->call('imageDestroy', array($this->_im));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the height and width of the current image data.
|
|
*
|
|
* @return array A hash with 'width' containing the width, 'height'
|
|
* containing the height of the image.
|
|
*/
|
|
public function getDimensions()
|
|
{
|
|
if (is_resource($this->_im) &&
|
|
$this->_width == 0 &&
|
|
$this->_height == 0) {
|
|
$this->_width = $this->call('imageSX', array($this->_im));
|
|
$this->_height = $this->call('imageSY', array($this->_im));
|
|
}
|
|
return array(
|
|
'width' => $this->_width,
|
|
'height' => $this->_height
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Creates a color that can be accessed in this object.
|
|
*
|
|
* When a color is set, the integer resource of it is returned.
|
|
*
|
|
* @param string $name The name of the color.
|
|
* @param integer $alpha Alpha transparency (0 - 127).
|
|
*
|
|
* @return integer The resource of the color that can be passed to GD.
|
|
*/
|
|
private function _allocateColor($name, $alpha = 0)
|
|
{
|
|
if (empty($this->_colors[$name])) {
|
|
if ($name == 'none') {
|
|
$this->_colors[$name] = $this->call(
|
|
'imageColorAllocateAlpha',
|
|
array($this->_im, 0, 0, 0, 127)
|
|
);
|
|
} else {
|
|
list($r, $g, $b) = Horde_Image::getRGB($name);
|
|
$this->_colors[$name] = $this->call(
|
|
'imageColorAllocateAlpha',
|
|
array($this->_im, $r, $g, $b, $alpha)
|
|
);
|
|
}
|
|
}
|
|
|
|
return $this->_colors[$name];
|
|
}
|
|
|
|
/**
|
|
* Returns the numeric font size a from textual description.
|
|
*
|
|
* @param string $font A textual size description.
|
|
*
|
|
* @return integer The font size supported by GD.
|
|
*/
|
|
private function _getFont($font)
|
|
{
|
|
switch ($font) {
|
|
case 'tiny':
|
|
return 1;
|
|
|
|
case 'medium':
|
|
return 3;
|
|
|
|
case 'large':
|
|
return 4;
|
|
|
|
case 'giant':
|
|
return 5;
|
|
|
|
case 'small':
|
|
default:
|
|
return 2;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Loads the image data from a string.
|
|
*
|
|
* @param string $image_data The data to use for the image.
|
|
*/
|
|
public function loadString($image_data)
|
|
{
|
|
$this->_im = $this->call('imageCreateFromString', array($image_data));
|
|
}
|
|
|
|
/**
|
|
* Loads the image data from a file.
|
|
*
|
|
* @param string $filename The full path and filename to the file to load
|
|
* the image data from.
|
|
*/
|
|
public function loadFile($filename)
|
|
{
|
|
$info = $this->call('getimagesize', array($filename));
|
|
if (is_array($info)) {
|
|
switch ($info[2]) {
|
|
case IMAGETYPE_GIF:
|
|
if (function_exists('imagecreatefromgif')) {
|
|
$this->_im = $this->call(
|
|
'imagecreatefromgif',
|
|
array($filename)
|
|
);
|
|
}
|
|
break;
|
|
case IMAGETYPE_JPEG:
|
|
$this->_im = $this->call(
|
|
'imagecreatefromjpeg',
|
|
array($filename)
|
|
);
|
|
break;
|
|
case IMAGETYPE_PNG:
|
|
$this->_im = $this->call(
|
|
'imagecreatefrompng',
|
|
array($filename)
|
|
);
|
|
break;
|
|
case IMAGETYPE_WBMP:
|
|
if (function_exists('imagecreatefromgwbmp')) {
|
|
$this->_im = $this->call(
|
|
'imagecreatefromgwbmp',
|
|
array($filename)
|
|
);
|
|
}
|
|
break;
|
|
case IMAGETYPE_XBM:
|
|
$this->_im = $this->call(
|
|
'imagecreatefromxbm',
|
|
array($filename)
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (is_resource($this->_im)) {
|
|
return;
|
|
}
|
|
|
|
parent::loadFile($filename);
|
|
$this->_im = $this->call(
|
|
'imageCreateFromString',
|
|
array($this->_data->__toString())
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Resizes the current image.
|
|
*
|
|
* @param integer $width The new width.
|
|
* @param integer $height The new height.
|
|
* @param boolean $ratio Maintain original aspect ratio.
|
|
* @param boolean $keepProfile Keep the image meta data (unused).
|
|
*/
|
|
public function resize($width, $height, $ratio = true, $keepProfile = false)
|
|
{
|
|
/* Abort if we're asked to divide by zero, truncate the image
|
|
* completely in either direction, or there is no image data. */
|
|
if (!$width || !$height || !is_resource($this->_im)) {
|
|
throw new Horde_Image_Exception('Unable to resize image.');
|
|
}
|
|
|
|
if ($ratio) {
|
|
if ($width / $height > $this->call('imageSX', array($this->_im)) / $this->call('imageSY', array($this->_im))) {
|
|
$width = $height
|
|
* $this->call('imageSX', array($this->_im))
|
|
/ $this->call('imageSY', array($this->_im));
|
|
} else {
|
|
$height = $width
|
|
* $this->call('imageSY', array($this->_im))
|
|
/ $this->call('imageSX', array($this->_im));
|
|
}
|
|
}
|
|
|
|
$im = $this->_im;
|
|
$this->_im = $this->create($width, $height);
|
|
|
|
/* Reset geometry since it will change. */
|
|
$this->_width = 0;
|
|
$this->_height = 0;
|
|
|
|
try {
|
|
$this->call(
|
|
'imageCopyResampled',
|
|
array(
|
|
$this->_im, $im,
|
|
0, 0, 0, 0,
|
|
$width, $height,
|
|
$this->call('imageSX', array($im)),
|
|
$this->call('imageSY', array($im))
|
|
)
|
|
);
|
|
} catch (Horde_Image_Exception $e) {
|
|
$this->call(
|
|
'imageCopyResized',
|
|
array(
|
|
$this->_im, $im,
|
|
0, 0, 0, 0,
|
|
$width, $height,
|
|
$this->call('imageSX', array($im)),
|
|
$this->call('imageSY', array($im))
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Crops the current image.
|
|
*
|
|
* @param integer $x1 x for the top left corner.
|
|
* @param integer $y1 y for the top left corner.
|
|
* @param integer $x2 x for the bottom right corner.
|
|
* @param integer $y2 y for the bottom right corner.
|
|
*/
|
|
public function crop($x1, $y1, $x2, $y2)
|
|
{
|
|
$im = $this->_im;
|
|
$this->_im = $this->create($x2 - $x1, $y2 - $y1);
|
|
$this->_width = 0;
|
|
$this->_height = 0;
|
|
$this->call(
|
|
'imageCopy',
|
|
array($this->_im, $im, 0, 0, $x1, $y1, $x2 - $x1, $y2 - $y1)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Rotates the current image.
|
|
*
|
|
* @param integer $angle The angle to rotate the image by, in the
|
|
* clockwise direction.
|
|
* @param string $background The background color to fill any triangles.
|
|
*/
|
|
public function rotate($angle, $background = 'white')
|
|
{
|
|
$background = $this->_allocateColor($background);
|
|
|
|
$this->_width = 0;
|
|
$this->_height = 0;
|
|
|
|
switch ($angle) {
|
|
case '90':
|
|
$x = $this->call('imageSX', array($this->_im));
|
|
$y = $this->call('imageSY', array($this->_im));
|
|
$xymax = max($x, $y);
|
|
|
|
$im = $this->create($xymax, $xymax);
|
|
$im = $this->call('imageRotate', array($im, 270, $background));
|
|
$this->_im = $im;
|
|
$im = $this->create($y, $x);
|
|
if ($x < $y) {
|
|
$this->call(
|
|
'imageCopy',
|
|
array($im, $this->_im, 0, 0, 0, 0, $xymax, $xymax)
|
|
);
|
|
} elseif ($x > $y) {
|
|
$this->call(
|
|
'imageCopy',
|
|
array(
|
|
$im, $this->_im,
|
|
0, 0, $xymax - $y, $xymax - $x,
|
|
$xymax, $xymax
|
|
)
|
|
);
|
|
}
|
|
$this->_im = $im;
|
|
break;
|
|
|
|
default:
|
|
$this->_im = $this->call(
|
|
'imageRotate',
|
|
array($this->_im, 360 - $angle, $background)
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Flips the current image.
|
|
*/
|
|
public function flip()
|
|
{
|
|
$x = $this->call('imageSX', array($this->_im));
|
|
$y = $this->call('imageSY', array($this->_im));
|
|
|
|
$im = $this->create($x, $y);
|
|
for ($curY = 0; $curY < $y; $curY++) {
|
|
$this->call(
|
|
'imageCopy',
|
|
array($im, $this->_im, 0, $y - ($curY + 1), 0, $curY, $x, 1)
|
|
);
|
|
}
|
|
|
|
$this->_im = $im;
|
|
}
|
|
|
|
/**
|
|
* Mirrors the current image.
|
|
*/
|
|
public function mirror()
|
|
{
|
|
$x = $this->call('imageSX', array($this->_im));
|
|
$y = $this->call('imageSY', array($this->_im));
|
|
|
|
$im = $this->create($x, $y);
|
|
for ($curX = 0; $curX < $x; $curX++) {
|
|
$this->call(
|
|
'imageCopy',
|
|
array($im, $this->_im, $x - ($curX + 1), 0, $curX, 0, 1, $y)
|
|
);
|
|
}
|
|
|
|
$this->_im = $im;
|
|
}
|
|
|
|
/**
|
|
* Converts the current image to grayscale.
|
|
*/
|
|
public function grayscale()
|
|
{
|
|
$rateR = .229;
|
|
$rateG = .587;
|
|
$rateB = .114;
|
|
$whiteness = 3;
|
|
if ($this->call('imageIsTrueColor', array($this->_im)) === true) {
|
|
$this->call('imageTrueColorToPalette', array($this->_im, true, 256));
|
|
}
|
|
$colors = min(256, $this->call('imageColorsTotal', array($this->_im)));
|
|
for ($x = 0; $x < $colors; $x++) {
|
|
$src = $this->call('imageColorsForIndex', array($this->_im, $x));
|
|
$new = min(
|
|
255,
|
|
abs($src['red'] * $rateR + $src['green'] * $rateG + $src['blue'] * $rateB) + $whiteness);
|
|
$this->call('imageColorSet', array($this->_im, $x, $new, $new, $new));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Applies a sepia filter.
|
|
*
|
|
* @param integer $threshold Extent of sepia effect.
|
|
*/
|
|
public function sepia($threshold = 85)
|
|
{
|
|
$tintR = 80;
|
|
$tintG = 43;
|
|
$tintB = -23;
|
|
$rateR = .229;
|
|
$rateG = .587;
|
|
$rateB = .114;
|
|
$whiteness = 3;
|
|
|
|
if ($this->call('imageIsTrueColor', array($this->_im)) === true) {
|
|
$this->call('imageTrueColorToPalette', array($this->_im, true, 256));
|
|
}
|
|
|
|
$colors = max(256, $this->call('imageColorsTotal', array($this->_im)));
|
|
for ($x = 0; $x < $colors; $x++) {
|
|
$src = $this->call('imageColorsForIndex', array($this->_im, $x));
|
|
$new = min(
|
|
255,
|
|
abs($src['red'] * $rateR + $src['green'] * $rateG + $src['blue'] * $rateB) + $whiteness
|
|
);
|
|
$r = min(255, $new + $tintR);
|
|
$g = min(255, $new + $tintG);
|
|
$b = min(255, $new + $tintB);
|
|
$this->call('imageColorSet', array($this->_im, $x, $r, $g, $b));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Applies a yellow filter.
|
|
*
|
|
* Adds a layer of yellow that can be transparent or solid. If $intensityY
|
|
* is 255 the image will be 0% transparent (solid).
|
|
*
|
|
* @param integer $intensityY How strong should the yellow (red and green)
|
|
* be? (0-255)
|
|
* @param integer $intensityB How weak should the blue be? (>= 2, in the
|
|
* positive limit it will be make BLUE 0)
|
|
*/
|
|
public function yellowize($intensityY = 50, $intensityB = 3)
|
|
{
|
|
if ($this->call('imageIsTrueColor', array($this->_im)) === true) {
|
|
$this->call('imageTrueColorToPalette', array($this->_im, true, 256));
|
|
}
|
|
|
|
$colors = max(256, $this->call('imageColorsTotal', array($this->_im)));
|
|
for ($x = 0; $x < $colors; $x++) {
|
|
$src = $this->call('imageColorsForIndex', array($this->_im, $x));
|
|
$r = min($src['red'] + $intensityY, 255);
|
|
$g = min($src['green'] + $intensityY, 255);
|
|
$b = max(($r + $g) / max($intensityB, 2), 0);
|
|
$this->call('imageColorSet', array($this->_im, $x, $r, $g, $b));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Draws a text string on the image in a specified location, with the
|
|
* specified style information.
|
|
*
|
|
* @param string $text The text to draw.
|
|
* @param integer $x The left x coordinate of the start of the
|
|
* text string.
|
|
* @param integer $y The top y coordinate of the start of the text
|
|
* string.
|
|
* @param string $font The font identifier you want to use for the
|
|
* text.
|
|
* @param string $color The color that you want the text displayed in.
|
|
* @param integer $direction An integer that specifies the orientation of
|
|
* the text.
|
|
* @param string $fontsize Size of the font (small, medium, large, giant)
|
|
*/
|
|
public function text(
|
|
$string, $x, $y, $font = '', $color = 'black', $direction = 0,
|
|
$fontsize = 'small'
|
|
)
|
|
{
|
|
$c = $this->_allocateColor($color);
|
|
$f = $this->_getFont($fontsize);
|
|
switch ($direction) {
|
|
case -90:
|
|
case 270:
|
|
$this->call(
|
|
'imageStringUp',
|
|
array($this->_im, $f, $x, $y, $string, $c)
|
|
);
|
|
break;
|
|
|
|
case 0:
|
|
default:
|
|
$this->call(
|
|
'imageString',
|
|
array($this->_im, $f, $x, $y, $string, $c)
|
|
);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Draws a circle.
|
|
*
|
|
* @param integer $x The x coordinate of the centre.
|
|
* @param integer $y The y coordinate of the centre.
|
|
* @param integer $r The radius of the circle.
|
|
* @param string $color The line color of the circle.
|
|
* @param string $fill The color to fill the circle.
|
|
*/
|
|
public function circle($x, $y, $r, $color, $fill = null)
|
|
{
|
|
$c = $this->_allocateColor($color);
|
|
if (is_null($fill)) {
|
|
$result = $this->call(
|
|
'imageEllipse',
|
|
array($this->_im, $x, $y, $r * 2, $r * 2, $c)
|
|
);
|
|
} else {
|
|
if ($fill !== $color) {
|
|
$fillColor = $this->_allocateColor($fill);
|
|
$this->call(
|
|
'imageFilledEllipse',
|
|
array($this->_im, $x, $y, $r * 2, $r * 2, $fillColor)
|
|
);
|
|
$this->call(
|
|
'imageEllipse',
|
|
array($this->_im, $x, $y, $r * 2, $r * 2, $c)
|
|
);
|
|
} else {
|
|
$this->call(
|
|
'imageFilledEllipse',
|
|
array($this->_im, $x, $y, $r * 2, $r * 2, $c)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Draws a polygon based on a set of vertices.
|
|
*
|
|
* @param array $vertices An array of x and y labeled arrays
|
|
* (eg. $vertices[0]['x'], $vertices[0]['y'], ...).
|
|
* @param string $color The color you want to draw the polygon with.
|
|
* @param string $fill The color to fill the polygon.
|
|
*/
|
|
public function polygon($verts, $color, $fill = 'none')
|
|
{
|
|
$vertices = array();
|
|
foreach ($verts as $vert) {
|
|
$vertices[] = $vert['x'];
|
|
$vertices[] = $vert['y'];
|
|
}
|
|
|
|
if ($fill != 'none') {
|
|
$f = $this->_allocateColor($fill);
|
|
$this->call(
|
|
'imageFilledPolygon',
|
|
array($this->_im, $vertices, count($verts), $f)
|
|
);
|
|
}
|
|
|
|
if ($fill == 'none' || $fill != $color) {
|
|
$c = $this->_allocateColor($color);
|
|
$this->call(
|
|
'imagePolygon',
|
|
array($this->_im, $vertices, count($verts), $c)
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Draws a rectangle.
|
|
*
|
|
* @param integer $x The left x-coordinate of the rectangle.
|
|
* @param integer $y The top y-coordinate of the rectangle.
|
|
* @param integer $width The width of the rectangle.
|
|
* @param integer $height The height of the rectangle.
|
|
* @param string $color The line color of the rectangle.
|
|
* @param string $fill The color to fill the rectangle.
|
|
*/
|
|
public function rectangle(
|
|
$x, $y, $width, $height, $color = 'black', $fill = 'none'
|
|
)
|
|
{
|
|
if ($fill != 'none') {
|
|
$f = $this->_allocateColor($fill);
|
|
$this->call(
|
|
'imageFilledRectangle',
|
|
array($this->_im, $x, $y, $x + $width, $y + $height, $f)
|
|
);
|
|
}
|
|
|
|
if ($fill == 'none' || $fill != $color) {
|
|
$c = $this->_allocateColor($color);
|
|
$this->call(
|
|
'imageRectangle',
|
|
array($this->_im, $x, $y, $x + $width, $y + $height, $c)
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Draws a rounded rectangle.
|
|
*
|
|
* @param integer $x The left x-coordinate of the rectangle.
|
|
* @param integer $y The top y-coordinate of the rectangle.
|
|
* @param integer $width The width of the rectangle.
|
|
* @param integer $height The height of the rectangle.
|
|
* @param integer $round The width of the corner rounding.
|
|
* @param string $color The line color of the rectangle.
|
|
* @param string $fill The color to fill the rounded rectangle with.
|
|
*/
|
|
public function roundedRectangle(
|
|
$x, $y, $width, $height, $round, $color = 'black', $fill = 'none'
|
|
)
|
|
{
|
|
if ($round <= 0) {
|
|
// Optimize out any calls with no corner rounding.
|
|
$this->rectangle($x, $y, $width, $height, $color, $fill);
|
|
return;
|
|
}
|
|
|
|
$c = $this->_allocateColor($color);
|
|
|
|
// Set corner points to avoid lots of redundant math.
|
|
$xul = $x + $round;
|
|
$yul = $y + $round;
|
|
|
|
$xur = $x + $width - $round;
|
|
$yur = $y + $round;
|
|
|
|
$xlr = $x + $width - $round;
|
|
$ylr = $y + $height - $round;
|
|
|
|
$xll = $x + $round;
|
|
$yll = $y + $height - $round;
|
|
|
|
$r = $round * 2;
|
|
|
|
// Calculate the upper left arc.
|
|
$pul = Horde_Image::arcPoints($round, 180, 270);
|
|
|
|
// Calculate the upper right arc.
|
|
$pur = Horde_Image::arcPoints($round, 270, 360);
|
|
|
|
// Calculate the lower right arc.
|
|
$plr = Horde_Image::arcPoints($round, 0, 90);
|
|
|
|
// Calculate the lower left arc.
|
|
$pll = Horde_Image::arcPoints($round, 90, 180);
|
|
|
|
// Draw the corners - upper left, upper right, lower right, lower left.
|
|
$this->call(
|
|
'imageArc',
|
|
array($this->_im, $xul, $yul, $r, $r, 180, 270, $c)
|
|
);
|
|
$this->call(
|
|
'imageArc',
|
|
array($this->_im, $xur, $yur, $r, $r, 270, 360, $c)
|
|
);
|
|
$this->call(
|
|
'imageArc',
|
|
array($this->_im, $xlr, $ylr, $r, $r, 0, 90, $c)
|
|
);
|
|
$this->call(
|
|
'imageArc',
|
|
array($this->_im, $xll, $yll, $r, $r, 90, 180, $c)
|
|
);
|
|
|
|
// Draw the connecting sides - top, right, bottom, left.
|
|
$this->call(
|
|
'imageLine',
|
|
array($this->_im, $xul, $y, $xur, $y, $c)
|
|
);
|
|
$this->call(
|
|
'imageLine',
|
|
array($this->_im, $x + $width, $yur, $x + $width, $ylr, $c)
|
|
);
|
|
$this->call(
|
|
'imageLine',
|
|
array($this->_im, $xlr, $y + $height, $xll, $y + $height, $c)
|
|
);
|
|
$this->call(
|
|
'imageLine',
|
|
array($this->_im, $x, $yll, $x, $yul, $c)
|
|
);
|
|
|
|
if ($fill != 'none') {
|
|
$f = $this->_allocateColor($fill);
|
|
$this->call(
|
|
'imageFillToBorder',
|
|
array(
|
|
$this->_im,
|
|
$x + ($width / 2), $y + ($height / 2),
|
|
$c, $f
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Draws a line.
|
|
*
|
|
* @param integer $x0 The x coordinate of the start.
|
|
* @param integer $y0 The y coordinate of the start.
|
|
* @param integer $x1 The x coordinate of the end.
|
|
* @param integer $y1 The y coordinate of the end.
|
|
* @param string $color The line color.
|
|
* @param string $width The width of the line.
|
|
*/
|
|
public function line($x1, $y1, $x2, $y2, $color = 'black', $width = 1)
|
|
{
|
|
$c = $this->_allocateColor($color);
|
|
|
|
// Don't need to do anything special for single-width lines.
|
|
if ($width == 1) {
|
|
$this->call('imageLine', array($this->_im, $x1, $y1, $x2, $y2, $c));
|
|
} elseif ($x1 == $x2) {
|
|
// For vertical lines, we can just draw a vertical rectangle.
|
|
$left = $x1 - floor(($width - 1) / 2);
|
|
$right = $x1 + floor($width / 2);
|
|
$this->call(
|
|
'imageFilledRectangle',
|
|
array($this->_im, $left, $y1, $right, $y2, $c)
|
|
);
|
|
} elseif ($y1 == $y2) {
|
|
// For horizontal lines, we can just draw a horizontal filled
|
|
// rectangle.
|
|
$top = $y1 - floor($width / 2);
|
|
$bottom = $y1 + floor(($width - 1) / 2);
|
|
$this->call(
|
|
'imageFilledRectangle',
|
|
array($this->_im, $x1, $top, $x2, $bottom, $c)
|
|
);
|
|
} else {
|
|
// Angled lines.
|
|
|
|
// Make sure that the end points of the line are perpendicular to
|
|
// the line itself.
|
|
$a = atan2($y1 - $y2, $x2 - $x1);
|
|
$dx = (sin($a) * $width / 2);
|
|
$dy = (cos($a) * $width / 2);
|
|
|
|
$verts = array(
|
|
$x2 + $dx, $y2 + $dy,
|
|
$x2 - $dx, $y2 - $dy,
|
|
$x1 - $dx, $y1 - $dy,
|
|
$x1 + $dx, $y1 + $dy
|
|
);
|
|
$this->call(
|
|
'imageFilledPolygon',
|
|
array($this->_im, $verts, count($verts) / 2, $c)
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Draws a dashed line.
|
|
*
|
|
* @param integer $x0 The x co-ordinate of the start.
|
|
* @param integer $y0 The y co-ordinate of the start.
|
|
* @param integer $x1 The x co-ordinate of the end.
|
|
* @param integer $y1 The y co-ordinate of the end.
|
|
* @param string $color The line color.
|
|
* @param string $width The width of the line.
|
|
* @param integer $dash_length The length of a dash on the dashed line.
|
|
* @param integer $dash_space The length of a space in the dashed line.
|
|
*/
|
|
public function dashedLine(
|
|
$x0, $y0, $x1, $y1, $color = 'black', $width = 1, $dash_length = 2,
|
|
$dash_space = 2
|
|
)
|
|
{
|
|
$c = $this->_allocateColor($color);
|
|
$w = $this->_allocateColor('white');
|
|
|
|
// Set up the style array according to the $dash_* parameters.
|
|
$style = array();
|
|
for ($i = 0; $i < $dash_length; $i++) {
|
|
$style[] = $c;
|
|
}
|
|
for ($i = 0; $i < $dash_space; $i++) {
|
|
$style[] = $w;
|
|
}
|
|
|
|
$this->call('imageSetStyle', array($this->_im, $style));
|
|
$this->call('imageSetThickness', array($this->_im, $width));
|
|
$this->call(
|
|
'imageLine',
|
|
array($this->_im, $x0, $y0, $x1, $y1, IMG_COLOR_STYLED)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Draws a polyline (a non-closed, non-filled polygon) based on a set of
|
|
* vertices.
|
|
*
|
|
* @param array $vertices An array of x and y labeled arrays
|
|
* (eg. $vertices[0]['x'], $vertices[0]['y'], ...).
|
|
* @param string $color The color you want to draw the line with.
|
|
* @param string $width The width of the line.
|
|
*/
|
|
public function polyline($verts, $color, $width = 1)
|
|
{
|
|
$first = true;
|
|
foreach ($verts as $vert) {
|
|
if (!$first) {
|
|
$this->line(
|
|
$lastX, $lastY,
|
|
$vert['x'], $vert['y'],
|
|
$color, $width
|
|
);
|
|
} else {
|
|
$first = false;
|
|
}
|
|
$lastX = $vert['x'];
|
|
$lastY = $vert['y'];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Draws an arc.
|
|
*
|
|
* @param integer $x The x coordinate of the centre.
|
|
* @param integer $y The y coordinate of the centre.
|
|
* @param integer $r The radius of the arc.
|
|
* @param integer $start The start angle of the arc.
|
|
* @param integer $end The end angle of the arc.
|
|
* @param string $color The line color of the arc.
|
|
* @param string $fill The fill color of the arc (defaults to none).
|
|
*/
|
|
public function arc(
|
|
$x, $y, $r, $start, $end, $color = 'black', $fill = null
|
|
)
|
|
{
|
|
$c = $this->_allocateColor($color);
|
|
if (is_null($fill)) {
|
|
$this->call(
|
|
'imageArc',
|
|
array($this->_im, $x, $y, $r * 2, $r * 2, $start, $end, $c)
|
|
);
|
|
} else {
|
|
if ($fill !== $color) {
|
|
$f = $this->_allocateColor($fill);
|
|
$this->call(
|
|
'imageFilledArc',
|
|
array(
|
|
$this->_im,
|
|
$x, $y, $r * 2, $r * 2,
|
|
$start, $end,
|
|
$f, IMG_ARC_PIE
|
|
)
|
|
);
|
|
$this->call(
|
|
'imageFilledArc',
|
|
array(
|
|
$this->_im,
|
|
$x, $y, $r * 2, $r * 2,
|
|
$start, $end,
|
|
$c, IMG_ARC_EDGED | IMG_ARC_NOFILL
|
|
)
|
|
);
|
|
} else {
|
|
$this->call(
|
|
'imageFilledArc',
|
|
array(
|
|
$this->_im,
|
|
$x, $y, $r * 2, $r * 2,
|
|
$start, $end,
|
|
$c, IMG_ARC_PIE
|
|
)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates an image of the given size.
|
|
*
|
|
* If possible the function returns a true color image.
|
|
*
|
|
* @param integer $width The image width.
|
|
* @param integer $height The image height.
|
|
*
|
|
* @return resource The image handler.
|
|
* @throws Horde_Image_Exception
|
|
*/
|
|
public function create($width, $height)
|
|
{
|
|
$result = $this->call('imageCreateTrueColor', array($width, $height));
|
|
if (!is_resource($result)) {
|
|
throw new Horde_Image_Exception('Could not create image.');
|
|
}
|
|
$this->call('imagesavealpha', array($result, true));
|
|
$this->call('imagealphablending', array($result, false));
|
|
if (function_exists('imageantialias')) {
|
|
$this->call('imageantialias', array($result, true));
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Wraps a call to a function of the gd extension.
|
|
*
|
|
* @param string $function The name of the function to wrap.
|
|
* @param array $params An array with all parameters for that function.
|
|
*
|
|
* @return mixed The result of the function call.
|
|
* @throws Horde_Image_Exception
|
|
*/
|
|
public function call($function, $params = null)
|
|
{
|
|
unset($php_errormsg);
|
|
$track = ini_set('track_errors', 1);
|
|
$result = @call_user_func_array($function, $params);
|
|
if ($track !== false) {
|
|
ini_set('track_errors', $track);
|
|
}
|
|
if (!empty($php_errormsg)) {
|
|
$error_msg = $php_errormsg;
|
|
throw new Horde_Image_Exception($error_msg);
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Applies the specified mask to the image.
|
|
*
|
|
* @param resource $mask The gd image resource representing the mask
|
|
*/
|
|
public function applyMask($mask)
|
|
{
|
|
$imgX = round($this->call('imageSX', array($this->_im)));
|
|
$imgY = round($this->call('imageSY', array($this->_im)));
|
|
$mask_resized = $this->create($imgX, $imgY);
|
|
$this->call(
|
|
'imageCopyResampled',
|
|
array(
|
|
$mask_resized, $mask,
|
|
0, 0, 0, 0,
|
|
$imgX, $imgY,
|
|
$this->call('imageSX', array($mask)),
|
|
$this->call('imageSY', array($mask))
|
|
)
|
|
);
|
|
|
|
$mask_blendtemp = $this->create($imgX, $imgY);
|
|
$mbtX = $this->call('imageSX', array($mask_blendtemp));
|
|
$mbtY = $this->call('imageSY', array($mask_blendtemp));
|
|
|
|
$color_background = $this->call(
|
|
'imageColorAllocate',
|
|
array($mask_blendtemp, 0, 0, 0)
|
|
);
|
|
|
|
$this->call(
|
|
'imageFilledRectangle',
|
|
array($mask_blendtemp, 0, 0, $mbtX, $mbtY, $color_background)
|
|
);
|
|
|
|
$this->call('imageAlphaBlending', array($mask_blendtemp, false));
|
|
$this->call('imageSaveAlpha', array($mask_blendtemp, true));
|
|
|
|
for ($x = 0; $x < $imgX; $x++) {
|
|
for ($y = 0; $y < $imgY; $y++) {
|
|
$realPixel = $this->call(
|
|
'imageColorsForIndex',
|
|
array(
|
|
$this->_im,
|
|
$this->call('imageColorAt', array($this->_im, $x, $y))
|
|
)
|
|
);
|
|
$maskPixel = Horde_Image::grayscalePixel(
|
|
$this->call(
|
|
'imageColorsForIndex',
|
|
array(
|
|
$mask_resized,
|
|
$this->call(
|
|
'imageColorAt',
|
|
array($mask_resized, $x, $y)
|
|
)
|
|
)
|
|
)
|
|
);
|
|
$maskAlpha = 127
|
|
- floor($maskPixel['red'] / 2)
|
|
* (1 - ($realPixel['alpha'] / 127));
|
|
$newcolor = $this->call(
|
|
'imageColorAllocateAlpha',
|
|
array(
|
|
$mask_blendtemp,
|
|
$realPixel['red'],
|
|
$realPixel['green'],
|
|
$realPixel['blue'],
|
|
intval($maskAlpha)
|
|
)
|
|
);
|
|
$this->call(
|
|
'imageSetPixel',
|
|
array($mask_blendtemp, $x, $y, $newcolor)
|
|
);
|
|
}
|
|
}
|
|
$this->call('imageAlphaBlending', array($this->_im, false));
|
|
$this->call('imageSaveAlpha', array($this->_im, true));
|
|
$this->call(
|
|
'imageCopy',
|
|
array($this->_im, $mask_blendtemp, 0, 0, 0, 0, $mbtX, $mbtY)
|
|
);
|
|
|
|
$this->call('imageDestroy', array($mask_blendtemp));
|
|
$this->call('imageDestroy', array($mask_resized));
|
|
}
|
|
|
|
}
|