|
1 <?php |
|
2 |
|
3 /** |
|
4 * @file |
|
5 * API for manipulating images. |
|
6 */ |
|
7 |
|
8 /** |
|
9 * @defgroup image Image toolkits |
|
10 * @{ |
|
11 * Functions for image file manipulations. |
|
12 * |
|
13 * Drupal's image toolkits provide an abstraction layer for common image file |
|
14 * manipulations like scaling, cropping, and rotating. The abstraction frees |
|
15 * module authors from the need to support multiple image libraries, and it |
|
16 * allows site administrators to choose the library that's best for them. |
|
17 * |
|
18 * PHP includes the GD library by default so a GD toolkit is installed with |
|
19 * Drupal. Other toolkits like ImageMagick are available from contrib modules. |
|
20 * GD works well for small images, but using it with larger files may cause PHP |
|
21 * to run out of memory. In contrast the ImageMagick library does not suffer |
|
22 * from this problem, but it requires the ISP to have installed additional |
|
23 * software. |
|
24 * |
|
25 * Image toolkits are discovered based on the associated module's |
|
26 * hook_image_toolkits. Additionally the image toolkit include file |
|
27 * must be identified in the files array in the module.info file. The |
|
28 * toolkit must then be enabled using the admin/config/media/image-toolkit |
|
29 * form. |
|
30 * |
|
31 * Only one toolkit may be selected at a time. If a module author wishes to call |
|
32 * a specific toolkit they can check that it is installed by calling |
|
33 * image_get_available_toolkits(), and then calling its functions directly. |
|
34 */ |
|
35 |
|
36 /** |
|
37 * Gets a list of available toolkits. |
|
38 * |
|
39 * @return |
|
40 * An array with the toolkit names as keys and the descriptions as values. |
|
41 */ |
|
42 function image_get_available_toolkits() { |
|
43 // hook_image_toolkits returns an array of toolkit names. |
|
44 $toolkits = module_invoke_all('image_toolkits'); |
|
45 |
|
46 $output = array(); |
|
47 foreach ($toolkits as $name => $info) { |
|
48 // Only allow modules that aren't marked as unavailable. |
|
49 if ($info['available']) { |
|
50 $output[$name] = $info['title']; |
|
51 } |
|
52 } |
|
53 |
|
54 return $output; |
|
55 } |
|
56 |
|
57 /** |
|
58 * Gets the name of the currently used toolkit. |
|
59 * |
|
60 * @return |
|
61 * String containing the name of the selected toolkit, or FALSE on error. |
|
62 */ |
|
63 function image_get_toolkit() { |
|
64 static $toolkit; |
|
65 |
|
66 if (!isset($toolkit)) { |
|
67 $toolkits = image_get_available_toolkits(); |
|
68 $toolkit = variable_get('image_toolkit', 'gd'); |
|
69 if (!isset($toolkits[$toolkit]) || !function_exists('image_' . $toolkit . '_load')) { |
|
70 // The selected toolkit isn't available so return the first one found. If |
|
71 // none are available this will return FALSE. |
|
72 reset($toolkits); |
|
73 $toolkit = key($toolkits); |
|
74 } |
|
75 } |
|
76 |
|
77 return $toolkit; |
|
78 } |
|
79 |
|
80 /** |
|
81 * Invokes the given method using the currently selected toolkit. |
|
82 * |
|
83 * @param $method |
|
84 * A string containing the method to invoke. |
|
85 * @param $image |
|
86 * An image object returned by image_load(). |
|
87 * @param $params |
|
88 * An optional array of parameters to pass to the toolkit method. |
|
89 * |
|
90 * @return |
|
91 * Mixed values (typically Boolean indicating successful operation). |
|
92 */ |
|
93 function image_toolkit_invoke($method, stdClass $image, array $params = array()) { |
|
94 $function = 'image_' . $image->toolkit . '_' . $method; |
|
95 if (function_exists($function)) { |
|
96 array_unshift($params, $image); |
|
97 return call_user_func_array($function, $params); |
|
98 } |
|
99 watchdog('image', 'The selected image handling toolkit %toolkit can not correctly process %function.', array('%toolkit' => $image->toolkit, '%function' => $function), WATCHDOG_ERROR); |
|
100 return FALSE; |
|
101 } |
|
102 |
|
103 /** |
|
104 * Gets details about an image. |
|
105 * |
|
106 * Drupal supports GIF, JPG and PNG file formats when used with the GD |
|
107 * toolkit, and may support others, depending on which toolkits are |
|
108 * installed. |
|
109 * |
|
110 * @param $filepath |
|
111 * String specifying the path of the image file. |
|
112 * @param $toolkit |
|
113 * An optional image toolkit name to override the default. |
|
114 * |
|
115 * @return |
|
116 * FALSE, if the file could not be found or is not an image. Otherwise, a |
|
117 * keyed array containing information about the image: |
|
118 * - "width": Width, in pixels. |
|
119 * - "height": Height, in pixels. |
|
120 * - "extension": Commonly used file extension for the image. |
|
121 * - "mime_type": MIME type ('image/jpeg', 'image/gif', 'image/png'). |
|
122 * - "file_size": File size in bytes. |
|
123 */ |
|
124 function image_get_info($filepath, $toolkit = FALSE) { |
|
125 $details = FALSE; |
|
126 if (!is_file($filepath) && !is_uploaded_file($filepath)) { |
|
127 return $details; |
|
128 } |
|
129 |
|
130 if (!$toolkit) { |
|
131 $toolkit = image_get_toolkit(); |
|
132 } |
|
133 if ($toolkit) { |
|
134 $image = new stdClass(); |
|
135 $image->source = $filepath; |
|
136 $image->toolkit = $toolkit; |
|
137 $details = image_toolkit_invoke('get_info', $image); |
|
138 if (isset($details) && is_array($details)) { |
|
139 $details['file_size'] = filesize($filepath); |
|
140 } |
|
141 } |
|
142 |
|
143 return $details; |
|
144 } |
|
145 |
|
146 /** |
|
147 * Scales an image to the exact width and height given. |
|
148 * |
|
149 * This function achieves the target aspect ratio by cropping the original image |
|
150 * equally on both sides, or equally on the top and bottom. This function is |
|
151 * useful to create uniform sized avatars from larger images. |
|
152 * |
|
153 * The resulting image always has the exact target dimensions. |
|
154 * |
|
155 * @param $image |
|
156 * An image object returned by image_load(). |
|
157 * @param $width |
|
158 * The target width, in pixels. |
|
159 * @param $height |
|
160 * The target height, in pixels. |
|
161 * |
|
162 * @return |
|
163 * TRUE on success, FALSE on failure. |
|
164 * |
|
165 * @see image_load() |
|
166 * @see image_resize() |
|
167 * @see image_crop() |
|
168 */ |
|
169 function image_scale_and_crop(stdClass $image, $width, $height) { |
|
170 $scale = max($width / $image->info['width'], $height / $image->info['height']); |
|
171 $x = ($image->info['width'] * $scale - $width) / 2; |
|
172 $y = ($image->info['height'] * $scale - $height) / 2; |
|
173 |
|
174 if (image_resize($image, $image->info['width'] * $scale, $image->info['height'] * $scale)) { |
|
175 return image_crop($image, $x, $y, $width, $height); |
|
176 } |
|
177 return FALSE; |
|
178 } |
|
179 |
|
180 /** |
|
181 * Scales image dimensions while maintaining aspect ratio. |
|
182 * |
|
183 * The resulting dimensions can be smaller for one or both target dimensions. |
|
184 * |
|
185 * @param $dimensions |
|
186 * Dimensions to be modified - an array with components width and height, in |
|
187 * pixels. |
|
188 * @param $width |
|
189 * The target width, in pixels. If this value is NULL then the scaling will be |
|
190 * based only on the height value. |
|
191 * @param $height |
|
192 * The target height, in pixels. If this value is NULL then the scaling will |
|
193 * be based only on the width value. |
|
194 * @param $upscale |
|
195 * Boolean indicating that images smaller than the target dimensions will be |
|
196 * scaled up. This generally results in a low quality image. |
|
197 * |
|
198 * @return |
|
199 * TRUE if $dimensions was modified, FALSE otherwise. |
|
200 * |
|
201 * @see image_scale() |
|
202 */ |
|
203 function image_dimensions_scale(array &$dimensions, $width = NULL, $height = NULL, $upscale = FALSE) { |
|
204 $aspect = $dimensions['height'] / $dimensions['width']; |
|
205 |
|
206 // Calculate one of the dimensions from the other target dimension, |
|
207 // ensuring the same aspect ratio as the source dimensions. If one of the |
|
208 // target dimensions is missing, that is the one that is calculated. If both |
|
209 // are specified then the dimension calculated is the one that would not be |
|
210 // calculated to be bigger than its target. |
|
211 if (($width && !$height) || ($width && $height && $aspect < $height / $width)) { |
|
212 $height = (int) round($width * $aspect); |
|
213 } |
|
214 else { |
|
215 $width = (int) round($height / $aspect); |
|
216 } |
|
217 |
|
218 // Don't upscale if the option isn't enabled. |
|
219 if (!$upscale && ($width >= $dimensions['width'] || $height >= $dimensions['height'])) { |
|
220 return FALSE; |
|
221 } |
|
222 |
|
223 $dimensions['width'] = $width; |
|
224 $dimensions['height'] = $height; |
|
225 return TRUE; |
|
226 } |
|
227 |
|
228 /** |
|
229 * Scales an image while maintaining aspect ratio. |
|
230 * |
|
231 * The resulting image can be smaller for one or both target dimensions. |
|
232 * |
|
233 * @param $image |
|
234 * An image object returned by image_load(). |
|
235 * @param $width |
|
236 * The target width, in pixels. If this value is NULL then the scaling will |
|
237 * be based only on the height value. |
|
238 * @param $height |
|
239 * The target height, in pixels. If this value is NULL then the scaling will |
|
240 * be based only on the width value. |
|
241 * @param $upscale |
|
242 * Boolean indicating that files smaller than the dimensions will be scaled |
|
243 * up. This generally results in a low quality image. |
|
244 * |
|
245 * @return |
|
246 * TRUE on success, FALSE on failure. |
|
247 * |
|
248 * @see image_dimensions_scale() |
|
249 * @see image_load() |
|
250 * @see image_scale_and_crop() |
|
251 */ |
|
252 function image_scale(stdClass $image, $width = NULL, $height = NULL, $upscale = FALSE) { |
|
253 $dimensions = $image->info; |
|
254 |
|
255 // Scale the dimensions - if they don't change then just return success. |
|
256 if (!image_dimensions_scale($dimensions, $width, $height, $upscale)) { |
|
257 return TRUE; |
|
258 } |
|
259 |
|
260 return image_resize($image, $dimensions['width'], $dimensions['height']); |
|
261 } |
|
262 |
|
263 /** |
|
264 * Resizes an image to the given dimensions (ignoring aspect ratio). |
|
265 * |
|
266 * @param $image |
|
267 * An image object returned by image_load(). |
|
268 * @param $width |
|
269 * The target width, in pixels. |
|
270 * @param $height |
|
271 * The target height, in pixels. |
|
272 * |
|
273 * @return |
|
274 * TRUE on success, FALSE on failure. |
|
275 * |
|
276 * @see image_load() |
|
277 * @see image_gd_resize() |
|
278 */ |
|
279 function image_resize(stdClass $image, $width, $height) { |
|
280 $width = (int) round($width); |
|
281 $height = (int) round($height); |
|
282 |
|
283 return image_toolkit_invoke('resize', $image, array($width, $height)); |
|
284 } |
|
285 |
|
286 /** |
|
287 * Rotates an image by the given number of degrees. |
|
288 * |
|
289 * @param $image |
|
290 * An image object returned by image_load(). |
|
291 * @param $degrees |
|
292 * The number of (clockwise) degrees to rotate the image. |
|
293 * @param $background |
|
294 * An hexadecimal integer specifying the background color to use for the |
|
295 * uncovered area of the image after the rotation. E.g. 0x000000 for black, |
|
296 * 0xff00ff for magenta, and 0xffffff for white. For images that support |
|
297 * transparency, this will default to transparent. Otherwise it will |
|
298 * be white. |
|
299 * |
|
300 * @return |
|
301 * TRUE on success, FALSE on failure. |
|
302 * |
|
303 * @see image_load() |
|
304 * @see image_gd_rotate() |
|
305 */ |
|
306 function image_rotate(stdClass $image, $degrees, $background = NULL) { |
|
307 return image_toolkit_invoke('rotate', $image, array($degrees, $background)); |
|
308 } |
|
309 |
|
310 /** |
|
311 * Crops an image to a rectangle specified by the given dimensions. |
|
312 * |
|
313 * @param $image |
|
314 * An image object returned by image_load(). |
|
315 * @param $x |
|
316 * The top left coordinate, in pixels, of the crop area (x axis value). |
|
317 * @param $y |
|
318 * The top left coordinate, in pixels, of the crop area (y axis value). |
|
319 * @param $width |
|
320 * The target width, in pixels. |
|
321 * @param $height |
|
322 * The target height, in pixels. |
|
323 * |
|
324 * @return |
|
325 * TRUE on success, FALSE on failure. |
|
326 * |
|
327 * @see image_load() |
|
328 * @see image_scale_and_crop() |
|
329 * @see image_gd_crop() |
|
330 */ |
|
331 function image_crop(stdClass $image, $x, $y, $width, $height) { |
|
332 $aspect = $image->info['height'] / $image->info['width']; |
|
333 if (empty($height)) $height = $width / $aspect; |
|
334 if (empty($width)) $width = $height * $aspect; |
|
335 |
|
336 $width = (int) round($width); |
|
337 $height = (int) round($height); |
|
338 |
|
339 return image_toolkit_invoke('crop', $image, array($x, $y, $width, $height)); |
|
340 } |
|
341 |
|
342 /** |
|
343 * Converts an image to grayscale. |
|
344 * |
|
345 * @param $image |
|
346 * An image object returned by image_load(). |
|
347 * |
|
348 * @return |
|
349 * TRUE on success, FALSE on failure. |
|
350 * |
|
351 * @see image_load() |
|
352 * @see image_gd_desaturate() |
|
353 */ |
|
354 function image_desaturate(stdClass $image) { |
|
355 return image_toolkit_invoke('desaturate', $image); |
|
356 } |
|
357 |
|
358 /** |
|
359 * Loads an image file and returns an image object. |
|
360 * |
|
361 * Any changes to the file are not saved until image_save() is called. |
|
362 * |
|
363 * @param $file |
|
364 * Path to an image file. |
|
365 * @param $toolkit |
|
366 * An optional, image toolkit name to override the default. |
|
367 * |
|
368 * @return |
|
369 * An image object or FALSE if there was a problem loading the file. The |
|
370 * image object has the following properties: |
|
371 * - 'source' - The original file path. |
|
372 * - 'info' - The array of information returned by image_get_info() |
|
373 * - 'toolkit' - The name of the image toolkit requested when the image was |
|
374 * loaded. |
|
375 * Image toolkits may add additional properties. The caller is advised not to |
|
376 * monkey about with them. |
|
377 * |
|
378 * @see image_save() |
|
379 * @see image_get_info() |
|
380 * @see image_get_available_toolkits() |
|
381 * @see image_gd_load() |
|
382 */ |
|
383 function image_load($file, $toolkit = FALSE) { |
|
384 if (!$toolkit) { |
|
385 $toolkit = image_get_toolkit(); |
|
386 } |
|
387 if ($toolkit) { |
|
388 $image = new stdClass(); |
|
389 $image->source = $file; |
|
390 $image->info = image_get_info($file, $toolkit); |
|
391 if (isset($image->info) && is_array($image->info)) { |
|
392 $image->toolkit = $toolkit; |
|
393 if (image_toolkit_invoke('load', $image)) { |
|
394 return $image; |
|
395 } |
|
396 } |
|
397 } |
|
398 return FALSE; |
|
399 } |
|
400 |
|
401 /** |
|
402 * Closes the image and saves the changes to a file. |
|
403 * |
|
404 * @param $image |
|
405 * An image object returned by image_load(). The object's 'info' property |
|
406 * will be updated if the file is saved successfully. |
|
407 * @param $destination |
|
408 * Destination path where the image should be saved. If it is empty the |
|
409 * original image file will be overwritten. |
|
410 * |
|
411 * @return |
|
412 * TRUE on success, FALSE on failure. |
|
413 * |
|
414 * @see image_load() |
|
415 * @see image_gd_save() |
|
416 */ |
|
417 function image_save(stdClass $image, $destination = NULL) { |
|
418 if (empty($destination)) { |
|
419 $destination = $image->source; |
|
420 } |
|
421 if ($return = image_toolkit_invoke('save', $image, array($destination))) { |
|
422 // Clear the cached file size and refresh the image information. |
|
423 clearstatcache(); |
|
424 $image->info = image_get_info($destination, $image->toolkit); |
|
425 |
|
426 if (drupal_chmod($destination)) { |
|
427 return $return; |
|
428 } |
|
429 } |
|
430 return FALSE; |
|
431 } |
|
432 |
|
433 /** |
|
434 * @} End of "defgroup image". |
|
435 */ |