1: <?php
2: /**
3: * This class defines the Horde_Image:: API, and also provides some
4: * utility functions, such as generating highlights of a color.
5: *
6: * Copyright 2002-2012 Horde LLC (http://www.horde.org/)
7: *
8: * See the enclosed file COPYING for license information (LGPL). If you
9: * did not receive this file, see http://www.horde.org/licenses/lgpl21.
10: *
11: * @author Chuck Hagenbuch <chuck@horde.org>
12: * @author Michael J. Rubinsky <mrubinsk@horde.org>
13: * @package Image
14: */
15: abstract class Horde_Image_Base extends EmptyIterator
16: {
17: /**
18: * Background color.
19: *
20: * @var string
21: */
22: protected $_background = 'white';
23:
24: /**
25: * Observers.
26: *
27: * @var array
28: */
29: protected $_observers = array();
30:
31:
32: /**
33: * The current image data.
34: *
35: * @var string
36: */
37: protected $_data = '';
38:
39: /**
40: * Logger
41: */
42: protected $_logger;
43:
44: /**
45: * The current width of the image data.
46: *
47: * @var integer
48: */
49: protected $_width = 0;
50:
51: /**
52: * The current height of the image data.
53: *
54: * @var integer
55: */
56: protected $_height = 0;
57:
58: /**
59: * A directory for temporary files.
60: *
61: * @var string
62: */
63: protected $_tmpdir;
64:
65: /**
66: * Array containing available Effects
67: *
68: * @var array
69: */
70: protected $_loadedEffects = array();
71:
72: /**
73: * What kind of images should ImageMagick generate? Defaults to 'png'.
74: *
75: * @var string
76: */
77: protected $_type = 'png';
78:
79: /**
80: * Cache the context
81: *
82: * @param array
83: */
84: protected $_context;
85:
86: /**
87: * Constructor.
88: *
89: * @param array $params The image object parameters. Values include:
90: *<pre>
91: * (optional)width - The desired image width
92: * (optional)height - The desired image height
93: * (optional)type - The image type (png, jpeg etc...) If not provided,
94: * or set by the setType method, any image output will
95: * be converted to the default image type of png.
96: * (optional)data - The image binary data.
97: *</pre>
98: * @param array $context The object context - configuration, injected objects
99: *<pre>
100: * (required)tmpdir - Temporary directory
101: * (optional)logger - The logger
102: *</pre>
103: * @throws InvalidArgumentException
104: */
105: protected function __construct($params, $context = array())
106: {
107: $this->_params = $params;
108: $this->_context = $context;
109:
110: if (empty($context['tmpdir'])) {
111: throw new InvalidArgumentException('A path to a temporary directory is required.');
112: }
113:
114: $this->_tmpdir = $context['tmpdir'];
115: if (isset($params['width'])) {
116: $this->_width = $params['width'];
117: }
118: if (isset($params['height'])) {
119: $this->_height = $params['height'];
120: }
121: if (!empty($params['type'])) {
122: // We only want the extension, not the full mimetype.
123: if (strpos($params['type'], 'image/') !== false) {
124: $params['type'] = substr($params['type'], 6);
125: }
126: $this->_type = $params['type'];
127: }
128:
129: if (!empty($context['logger'])) {
130: $this->_logger = $context['logger'];
131: }
132:
133: $this->_background = isset($params['background']) ? $params['background'] : 'white';
134: }
135:
136: /**
137: * Getter for the capabilities array
138: *
139: * @return array
140: */
141: public function getCapabilities()
142: {
143: return $this->_capabilities;
144: }
145:
146: /**
147: * Check the existence of a particular capability.
148: *
149: * @param string $capability The capability to check for.
150: *
151: * @return boolean
152: */
153: public function hasCapability($capability)
154: {
155: return in_array($capability, $this->_capabilities);
156: }
157:
158: /**
159: * Generate image headers.
160: */
161: public function headers()
162: {
163: header('Content-type: ' . $this->getContentType());
164: }
165:
166: /**
167: * Return the content type for this image.
168: *
169: * @return string The content type for this image.
170: */
171: public function getContentType()
172: {
173: return 'image/' . $this->_type;
174: }
175:
176: /**
177: * Getter for the simplified image type.
178: *
179: * @return string The type of image (png, jpg, etc...)
180: */
181: public function getType()
182: {
183: return $this->_type;
184: }
185:
186: /**
187: * Setter for the image type.
188: *
189: * @param string $type The simple type for the imag (png, jpg, etc...)
190: *
191: * @return void
192: */
193: public function setType($type)
194: {
195: // We only want the extension, not the full mimetype.
196: if (strpos($type, 'image/') !== false) {
197: $type = substr($type, 6);
198: }
199: $old = $this->_type;
200: $this->_type = $type;
201:
202: return $old;
203: }
204:
205: /**
206: * Draw a shaped point at the specified (x,y) point. Useful for
207: * scatter diagrams, debug points, etc. Draws squares, circles,
208: * diamonds, and triangles.
209: *
210: * @param integer $x The x coordinate of the point to brush.
211: * @param integer $y The y coordinate of the point to brush.
212: * @param string $color The color to brush the point with.
213: * @param string $shape What brush to use? Defaults to a square.
214: */
215: public function brush($x, $y, $color = 'black', $shape = 'square')
216: {
217: switch ($shape) {
218: case 'triangle':
219: $verts[0] = array('x' => $x + 3, 'y' => $y + 3);
220: $verts[1] = array('x' => $x, 'y' => $y - 3);
221: $verts[2] = array('x' => $x - 3, 'y' => $y + 3);
222: $this->polygon($verts, $color, $color);
223: break;
224:
225: case 'circle':
226: $this->circle($x, $y, 3, $color, $color);
227: break;
228:
229: case 'diamond':
230: $verts[0] = array('x' => $x - 3, 'y' => $y);
231: $verts[1] = array('x' => $x, 'y' => $y + 3);
232: $verts[2] = array('x' => $x + 3, 'y' => $y);
233: $verts[3] = array('x' => $x, 'y' => $y - 3);
234: $this->polygon($verts, $color, $color);
235: break;
236:
237: case 'square':
238: default:
239: $this->rectangle($x - 2, $y - 2, 4, 4, $color, $color);
240: break;
241: }
242: }
243:
244: /**
245: * Reset the image data to defaults.
246: */
247: public function reset()
248: {
249: $this->_data = '';
250: $this->_width = null;
251: $this->_height = null;
252: $this->_background = 'white';
253: }
254:
255: /**
256: * Get the height and width of the current image data.
257: *
258: * @return array An hash with 'width' containing the width,
259: * 'height' containing the height of the image.
260: */
261: public function getDimensions()
262: {
263: // Check if we know it already
264: if ($this->_width == 0 && $this->_height == 0) {
265: $tmp = $this->toFile();
266: $details = @getimagesize($tmp);
267: list($this->_width, $this->_height) = $details;
268: unlink($tmp);
269: }
270:
271: return array('width' => $this->_width,
272: 'height' => $this->_height);
273: }
274:
275: /**
276: * Load the image data from a string.
277: *
278: * @param string $id An arbitrary id for the image.
279: * @param string $image_data The data to use for the image.
280: */
281: public function loadString($image_data)
282: {
283: $this->reset();
284: $this->_data = $image_data;
285: }
286:
287: /**
288: * Load the image data from a file.
289: *
290: * @param string $filename The full path and filename to the file to load
291: * the image data from. The filename will also be
292: * used for the image id.
293: *
294: * @return mixed True if successful or already loaded, PEAR Error if file
295: * does not exist or could not be loaded.
296: * @throws Horde_Image_Exception
297: */
298: public function loadFile($filename)
299: {
300: $this->reset();
301: if (!file_exists($filename)) {
302: throw new Horde_Image_Exception(sprintf("The image file, %s, does not exist.", $filename));
303: }
304: if (!$this->_data = file_get_contents($filename)) {
305: throw new Horde_Image_Exception(sprintf("Could not load the image file %s", $filename));
306: }
307:
308: return true;
309: }
310:
311: /**
312: * Ouputs image data to file. If $data is false, outputs current
313: * image data after performing any pending operations on the data.
314: * If $data contains raw image data, outputs that data to file without
315: * regard for $this->_data
316: *
317: * @param mixed String of binary image data | false
318: *
319: * @return string Path to temporary file.
320: */
321: public function toFile($data = false)
322: {
323: $tmp = Horde_Util::getTempFile('img', false, $this->_tmpdir);
324: $fp = @fopen($tmp, 'wb');
325: fwrite($fp, $data ? $data : $this->raw());
326: fclose($fp);
327: return $tmp;
328: }
329:
330: /**
331: * Display the current image.
332: */
333: public function display()
334: {
335: $this->headers();
336: echo $this->raw(true);
337: }
338:
339: /**
340: * Returns the raw data for this image.
341: *
342: * @param boolean $convert If true, the image data will be returned in the
343: * target format, independently from any image
344: * operations.
345: *
346: * @return string The raw image data.
347: */
348: public function raw($convert = false)
349: {
350: return $this->_data;
351: }
352:
353: /**
354: * Attempts to apply requested effect to this image.
355: *
356: * @param string $type The type of effect to apply.
357: * @param array $params Any parameters for the effect.
358: *
359: * @return boolean
360: */
361: public function addEffect($type, $params)
362: {
363: $class = str_replace('Horde_Image_', '', get_class($this));
364: $params['logger'] = $this->_logger;
365: $effect = Horde_Image_Effect::factory($type, $class, $params);
366: $effect->setImageObject($this);
367: return $effect->apply();
368: }
369:
370: /**
371: * Load a list of available effects for this driver.
372: */
373: public function getLoadedEffects()
374: {
375: if (!count($this->_loadedEffects)) {
376: $class = str_replace('Horde_Image_', '', get_class($this));
377: $this->_loadedEffects = array();
378: // First, load the driver-agnostic Effects.
379: $path = dirname(__FILE__) . '/Effect/';
380: if (is_dir($path)) {
381: if ($handle = opendir($path)) {
382: while (($file = readdir($handle)) !== false) {
383: if (substr($file, -4, 4) == '.php') {
384: $this->_loadedEffects[] = substr($file, 0, strlen($file) - 4);
385: }
386: }
387: }
388: }
389:
390: // Driver specific effects.
391: $path = $path . $class;
392: if (is_dir($path)) {
393: if ($handle = opendir($path)) {
394: while (($file = readdir($handle)) !== false) {
395: if (substr($file, -4, 4) == '.php') {
396: $this->_loadedEffects[] = substr($file, 0, strlen($file) - 4);
397: }
398: }
399: }
400: }
401: }
402:
403: return $this->_loadedEffects;
404: }
405:
406: /**
407: * Apply any effects in the effect queue.
408: */
409: public function applyEffects()
410: {
411: $this->raw();
412: }
413:
414: public function getTmpDir()
415: {
416: return $this->_tmpdir;
417: }
418:
419: /**
420: * Utility function to zero out cached geometry information. Shouldn't
421: * really be called from client code, but is needed since Effects may need
422: * to clear these.
423: *
424: */
425: public function clearGeometry()
426: {
427: $this->_height = 0;
428: $this->_width = 0;
429: }
430:
431: protected function _logDebug($message)
432: {
433: if (!empty($this->_logger)) {
434: $this->_logger->debug($message);
435: }
436: }
437:
438: protected function _logErr($message)
439: {
440: if (!empty($this->_logger)) {
441: $this->_logger->err($message);
442: }
443: }
444:
445: /**
446: * Request a specific image from the collection of images.
447: *
448: * @param integer $index The index to return
449: *
450: * @return Horde_Image_Base
451: */
452: abstract function getImageAtIndex($index);
453:
454: /**
455: * Return the number of image pages available in the image object.
456: *
457: * @return integer
458: */
459: abstract function getImagePageCount();
460:
461: }
462: