[TASK] Update copyright year to 2013
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / Imaging / GraphicalFunctions.php
1 <?php
2 namespace TYPO3\CMS\Core\Imaging;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 1999-2013 Kasper Skårhøj (kasperYYYY@typo3.com)
8 * All rights reserved
9 *
10 * This script is part of the TYPO3 project. The TYPO3 project is
11 * free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * The GNU General Public License can be found at
17 * http://www.gnu.org/copyleft/gpl.html.
18 * A copy is found in the textfile GPL.txt and important notices to the license
19 * from the author is found in LICENSE.txt distributed with these scripts.
20 *
21 *
22 * This script is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * This copyright notice MUST APPEAR in all copies of the script!
28 ***************************************************************/
29 /**
30 * Standard graphical functions
31 *
32 * Revised for TYPO3 3.6 July/2003 by Kasper Skårhøj
33 *
34 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
35 */
36 /**
37 * Class contains a bunch of cool functions for manipulating graphics with GDlib/Freetype and ImageMagick
38 * VERY OFTEN used with gifbuilder that extends this class and provides a TypoScript API to using these functions
39 *
40 * With TYPO3 4.4 GDlib 1.x support was dropped, also an option from $TYPO3_CONF_VARS
41 * $TYPO3_CONF_VARS['GFX']['gdlib_2'] = 0, // String/Boolean. Set this if you are using the new GDlib 2.0.1+. If you don't set this flag and still use GDlib2, you might encounter strange behaviours like black images etc. This feature might take effect only if ImageMagick is installed and working as well! You can also use the value "no_imagecopyresized_fix" - in that case it will NOT try to fix a known issue where "imagecopyresized" does not work correctly.
42 *
43 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
44 * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder
45 */
46 class GraphicalFunctions {
47
48 // Internal configuration, set in init()
49 // The ImageMagick filename used for combining two images. This name changed during the versions.
50 /**
51 * @todo Define visibility
52 */
53 public $combineScript = 'combine';
54
55 // If set, there is no frame pointer prepended to the filenames.
56 /**
57 * @todo Define visibility
58 */
59 public $noFramePrepended = 0;
60
61 // If set, imagecopyresized will not be called directly. For GD2 (some PHP installs?)
62 /**
63 * @todo Define visibility
64 */
65 public $imagecopyresized_fix = 0;
66
67 // This should be changed to 'png' if you want this class to read/make PNG-files instead!
68 /**
69 * @todo Define visibility
70 */
71 public $gifExtension = 'gif';
72
73 // File formats supported by gdlib. This variable get's filled in "init" method
74 /**
75 * @todo Define visibility
76 */
77 public $gdlibExtensions = '';
78
79 // Set to TRUE if generated png's should be truecolor by default
80 /**
81 * @todo Define visibility
82 */
83 public $png_truecolor = FALSE;
84
85 /**
86 * defines the RGB colorspace to use
87 *
88 * @var string
89 */
90 protected $colorspace = 'RGB';
91
92 /**
93 * colorspace names allowed
94 *
95 * @var array
96 */
97 protected $allowedColorSpaceNames = array(
98 'CMY',
99 'CMYK',
100 'Grey',
101 'HCL',
102 'HSB',
103 'HSL',
104 'HWB',
105 'Lab',
106 'LCH',
107 'LMS',
108 'Log',
109 'Luv',
110 'OHTA',
111 'Rec601Luma',
112 'Rec601YCbCr',
113 'Rec709Luma',
114 'Rec709YCbCr',
115 'RGB',
116 'sRGB',
117 'Transparent',
118 'XYZ',
119 'YCbCr',
120 'YCC',
121 'YIQ',
122 'YCbCr',
123 'YUV'
124 );
125
126 // 16777216 Colors is the maximum value for PNG, JPEG truecolor images (24-bit, 8-bit / Channel)
127 /**
128 * @todo Define visibility
129 */
130 public $truecolorColors = 16777215;
131
132 // If set, then all files in typo3temp will be logged in a database table. In addition to being a log of the files with original filenames, it also serves to secure that the same image is not rendered simultaneously by two different processes.
133 /**
134 * @todo Define visibility
135 */
136 public $enable_typo3temp_db_tracking = 0;
137
138 // Commalist of file extensions perceived as images by TYPO3. List should be set to 'gif,png,jpeg,jpg' if IM is not available. Lowercase and no spaces between!
139 /**
140 * @todo Define visibility
141 */
142 public $imageFileExt = 'gif,jpg,jpeg,png,tif,bmp,tga,pcx,ai,pdf';
143
144 // Commalist of web image extensions (can be shown by a webbrowser)
145 /**
146 * @todo Define visibility
147 */
148 public $webImageExt = 'gif,jpg,jpeg,png';
149
150 // Will be ' -negate' if ImageMagick ver 5.2+. See init();
151 /**
152 * @todo Define visibility
153 */
154 public $maskNegate = '';
155
156 /**
157 * @todo Define visibility
158 */
159 public $NO_IM_EFFECTS = '';
160
161 /**
162 * @todo Define visibility
163 */
164 public $cmds = array(
165 'jpg' => '',
166 'jpeg' => '',
167 'gif' => '',
168 'png' => '-colors 64'
169 );
170
171 /**
172 * @todo Define visibility
173 */
174 public $NO_IMAGE_MAGICK = '';
175
176 /**
177 * @todo Define visibility
178 */
179 public $V5_EFFECTS = 0;
180
181 /**
182 * @todo Define visibility
183 */
184 public $mayScaleUp = 1;
185
186 // Variables for testing, alternative usage etc.
187 // Filename prefix for images scaled in imageMagickConvert()
188 /**
189 * @todo Define visibility
190 */
191 public $filenamePrefix = '';
192
193 // Forcing the output filename of imageMagickConvert() to this value. However after calling imageMagickConvert() it will be set blank again.
194 /**
195 * @todo Define visibility
196 */
197 public $imageMagickConvert_forceFileNameBody = '';
198
199 // This flag should always be FALSE. If set TRUE, imageMagickConvert will always write a new file to the tempdir! Used for debugging.
200 /**
201 * @todo Define visibility
202 */
203 public $dontCheckForExistingTempFile = 0;
204
205 // Prevents imageMagickConvert() from compressing the gif-files with \TYPO3\CMS\Core\Utility\GeneralUtility::gif_compress()
206 /**
207 * @todo Define visibility
208 */
209 public $dontCompress = 0;
210
211 // For debugging ONLY!
212 /**
213 * @todo Define visibility
214 */
215 public $dontUnlinkTempFiles = 0;
216
217 // For debugging only. Filenames will not be based on mtime and only filename (not path) will be used. This key is also included in the hash of the filename...
218 /**
219 * @todo Define visibility
220 */
221 public $alternativeOutputKey = '';
222
223 // Internal:
224 // All ImageMagick commands executed is stored in this array for tracking. Used by the Install Tools Image section
225 /**
226 * @todo Define visibility
227 */
228 public $IM_commands = array();
229
230 /**
231 * @todo Define visibility
232 */
233 public $workArea = array();
234
235 /**
236 * Preserve the alpha transparency layer of read PNG images
237 *
238 * @var boolean
239 */
240 protected $saveAlphaLayer = FALSE;
241
242 // Constants:
243 // The temp-directory where to store the files. Normally relative to PATH_site but is allowed to be the absolute path AS LONG AS it is a subdir to PATH_site.
244 /**
245 * @todo Define visibility
246 */
247 public $tempPath = 'typo3temp/';
248
249 // Prefix for relative paths. Used in "show_item.php" script. Is prefixed the output file name IN imageMagickConvert()
250 /**
251 * @todo Define visibility
252 */
253 public $absPrefix = '';
254
255 // ImageMagick scaling command; "-geometry" eller "-sample". Used in makeText() and imageMagickConvert()
256 /**
257 * @todo Define visibility
258 */
259 public $scalecmd = '-geometry';
260
261 // Used by v5_blur() to simulate 10 continuous steps of blurring
262 /**
263 * @todo Define visibility
264 */
265 public $im5fx_blurSteps = '1x2,2x2,3x2,4x3,5x3,5x4,6x4,7x5,8x5,9x5';
266
267 // Used by v5_sharpen() to simulate 10 continuous steps of sharpening.
268 /**
269 * @todo Define visibility
270 */
271 public $im5fx_sharpenSteps = '1x2,2x2,3x2,2x3,3x3,4x3,3x4,4x4,4x5,5x5';
272
273 // This is the limit for the number of pixels in an image before it will be rendered as JPG instead of GIF/PNG
274 /**
275 * @todo Define visibility
276 */
277 public $pixelLimitGif = 10000;
278
279 // Array mapping HTML color names to RGB values.
280 /**
281 * @todo Define visibility
282 */
283 public $colMap = array(
284 'aqua' => array(0, 255, 255),
285 'black' => array(0, 0, 0),
286 'blue' => array(0, 0, 255),
287 'fuchsia' => array(255, 0, 255),
288 'gray' => array(128, 128, 128),
289 'green' => array(0, 128, 0),
290 'lime' => array(0, 255, 0),
291 'maroon' => array(128, 0, 0),
292 'navy' => array(0, 0, 128),
293 'olive' => array(128, 128, 0),
294 'purple' => array(128, 0, 128),
295 'red' => array(255, 0, 0),
296 'silver' => array(192, 192, 192),
297 'teal' => array(0, 128, 128),
298 'yellow' => array(255, 255, 0),
299 'white' => array(255, 255, 255)
300 );
301
302 /**
303 * Charset conversion object:
304 *
305 * @var \TYPO3\CMS\Core\Charset\CharsetConverter
306 * @todo Define visibility
307 */
308 public $csConvObj;
309
310 // Is set to the native character set of the input strings.
311 /**
312 * @todo Define visibility
313 */
314 public $nativeCharset = '';
315
316 /**
317 * Init function. Must always call this when using the class.
318 * This function will read the configuration information from $GLOBALS['TYPO3_CONF_VARS']['GFX'] can set some values in internal variables.
319 *
320 * @return void
321 * @todo Define visibility
322 */
323 public function init() {
324 $gfxConf = $GLOBALS['TYPO3_CONF_VARS']['GFX'];
325 if (function_exists('imagecreatefromjpeg') && function_exists('imagejpeg')) {
326 $this->gdlibExtensions .= ',jpg,jpeg';
327 }
328 if (function_exists('imagecreatefrompng') && function_exists('imagepng')) {
329 $this->gdlibExtensions .= ',png';
330 }
331 if (function_exists('imagecreatefromgif') && function_exists('imagegif')) {
332 $this->gdlibExtensions .= ',gif';
333 }
334 if ($gfxConf['png_truecolor']) {
335 $this->png_truecolor = TRUE;
336 }
337
338 if ($gfxConf['colorspace'] && in_array($gfxConf['colorspace'], $this->allowedColorSpaceNames, TRUE)) {
339 $this->colorspace = $gfxConf['colorspace'];
340 }
341
342 if (!$gfxConf['im']) {
343 $this->NO_IMAGE_MAGICK = 1;
344 }
345 if (!$this->NO_IMAGE_MAGICK && (!$gfxConf['im_version_5'] || $gfxConf['im_version_5'] === 'im4' || $gfxConf['im_version_5'] === 'im5')) {
346 throw new \RuntimeException('Your TYPO3 installation is configured to use an old version of ImageMagick, which is not supported anymore. ' . 'Please upgrade to ImageMagick version 6 or GraphicksMagick and set $TYPO3_CONF_VARS[\'GFX\'][\'im_version_5\'] appropriately.', 1305059666);
347 }
348 // When GIFBUILDER gets used in truecolor mode
349 // No colors parameter if we generate truecolor images.
350 if ($this->png_truecolor) {
351 $this->cmds['png'] = '';
352 }
353 // Setting default JPG parameters:
354 $this->jpegQuality = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($gfxConf['jpg_quality'], 10, 100, 75);
355 $this->cmds['jpg'] = ($this->cmds['jpeg'] = '-colorspace ' . $this->colorspace . ' -sharpen 50 -quality ' . $this->jpegQuality);
356 if ($gfxConf['im_combine_filename']) {
357 $this->combineScript = $gfxConf['im_combine_filename'];
358 }
359 if ($gfxConf['im_noFramePrepended']) {
360 $this->noFramePrepended = 1;
361 }
362 // Kept for backwards compatibility, can be turned on manually through localconf.php,
363 // but not through the installer anymore
364 $this->imagecopyresized_fix = $gfxConf['gdlib_2'] === 'no_imagecopyresized_fix' ? 0 : 1;
365 if ($gfxConf['gdlib_png']) {
366 $this->gifExtension = 'png';
367 }
368 if ($gfxConf['enable_typo3temp_db_tracking']) {
369 $this->enable_typo3temp_db_tracking = $gfxConf['enable_typo3temp_db_tracking'];
370 }
371 $this->imageFileExt = $gfxConf['imagefile_ext'];
372 // This should be set if ImageMagick ver. 5+ is used.
373 if ($gfxConf['im_negate_mask']) {
374 // Boolean. Indicates if the mask images should be inverted first.
375 // This depends of the ImageMagick version. Below ver. 5.1 this should be FALSE.
376 // Above ImageMagick version 5.2+ it should be TRUE.
377 // Just set the flag if the masks works opposite the intension!
378 $this->maskNegate = ' -negate';
379 }
380 if ($gfxConf['im_no_effects']) {
381 // Boolean. This is necessary if using ImageMagick 5+.
382 // Effects in Imagemagick 5+ tends to render very slowly!!
383 // - therefore must be disabled in order not to perform sharpen, blurring and such.
384 $this->NO_IM_EFFECTS = 1;
385 $this->cmds['jpg'] = ($this->cmds['jpeg'] = '-colorspace ' . $this->colorspace . ' -quality ' . $this->jpegQuality);
386 }
387 // ... but if 'im_v5effects' is set, don't care about 'im_no_effects'
388 if ($gfxConf['im_v5effects']) {
389 $this->NO_IM_EFFECTS = 0;
390 $this->V5_EFFECTS = 1;
391 if ($gfxConf['im_v5effects'] > 0) {
392 $this->cmds['jpg'] = ($this->cmds['jpeg'] = '-colorspace ' . $this->colorspace . ' -quality ' . intval($gfxConf['jpg_quality']) . $this->v5_sharpen(10));
393 }
394 }
395 // Secures that images are not scaled up.
396 if ($gfxConf['im_noScaleUp']) {
397 $this->mayScaleUp = 0;
398 }
399 if (TYPO3_MODE == 'FE') {
400 $this->csConvObj = $GLOBALS['TSFE']->csConvObj;
401 } elseif (is_object($GLOBALS['LANG'])) {
402 // BE assumed:
403 $this->csConvObj = $GLOBALS['LANG']->csConvObj;
404 } else {
405 // The object may not exist yet, so we need to create it now. Happens in the Install Tool for example.
406 $this->csConvObj = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Charset\\CharsetConverter');
407 }
408 $this->nativeCharset = 'utf-8';
409 }
410
411 /*************************************************
412 *
413 * Layering images / "IMAGE" GIFBUILDER object
414 *
415 *************************************************/
416 /**
417 * Implements the "IMAGE" GIFBUILDER object, when the "mask" property is TRUE.
418 * It reads the two images defined by $conf['file'] and $conf['mask'] and copies the $conf['file'] onto the input image pointer image using the $conf['mask'] as a grayscale mask
419 * The operation involves ImageMagick for combining.
420 *
421 * @param pointer $im GDlib image pointer
422 * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
423 * @param array $workArea The current working area coordinates.
424 * @return void
425 * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
426 * @todo Define visibility
427 */
428 public function maskImageOntoImage(&$im, $conf, $workArea) {
429 if ($conf['file'] && $conf['mask']) {
430 $imgInf = pathinfo($conf['file']);
431 $imgExt = strtolower($imgInf['extension']);
432 if (!\TYPO3\CMS\Core\Utility\GeneralUtility::inList($this->gdlibExtensions, $imgExt)) {
433 $BBimage = $this->imageMagickConvert($conf['file'], $this->gifExtension);
434 } else {
435 $BBimage = $this->getImageDimensions($conf['file']);
436 }
437 $maskInf = pathinfo($conf['mask']);
438 $maskExt = strtolower($maskInf['extension']);
439 if (!\TYPO3\CMS\Core\Utility\GeneralUtility::inList($this->gdlibExtensions, $maskExt)) {
440 $BBmask = $this->imageMagickConvert($conf['mask'], $this->gifExtension);
441 } else {
442 $BBmask = $this->getImageDimensions($conf['mask']);
443 }
444 if ($BBimage && $BBmask) {
445 $w = imagesx($im);
446 $h = imagesy($im);
447 $tmpStr = $this->randomName();
448 $theImage = $tmpStr . '_img.' . $this->gifExtension;
449 $theDest = $tmpStr . '_dest.' . $this->gifExtension;
450 $theMask = $tmpStr . '_mask.' . $this->gifExtension;
451 // Prepare overlay image
452 $cpImg = $this->imageCreateFromFile($BBimage[3]);
453 $destImg = imagecreatetruecolor($w, $h);
454 // Preserve alpha transparency
455 if ($this->saveAlphaLayer) {
456 imagesavealpha($destImg, TRUE);
457 $Bcolor = imagecolorallocatealpha($destImg, 0, 0, 0, 127);
458 imagefill($destImg, 0, 0, $Bcolor);
459 } else {
460 $Bcolor = ImageColorAllocate($destImg, 0, 0, 0);
461 ImageFilledRectangle($destImg, 0, 0, $w, $h, $Bcolor);
462 }
463 $this->copyGifOntoGif($destImg, $cpImg, $conf, $workArea);
464 $this->ImageWrite($destImg, $theImage);
465 imageDestroy($cpImg);
466 imageDestroy($destImg);
467 // Prepare mask image
468 $cpImg = $this->imageCreateFromFile($BBmask[3]);
469 $destImg = imagecreatetruecolor($w, $h);
470 if ($this->saveAlphaLayer) {
471 imagesavealpha($destImg, TRUE);
472 $Bcolor = imagecolorallocatealpha($destImg, 0, 0, 0, 127);
473 imagefill($destImg, 0, 0, $Bcolor);
474 } else {
475 $Bcolor = ImageColorAllocate($destImg, 0, 0, 0);
476 ImageFilledRectangle($destImg, 0, 0, $w, $h, $Bcolor);
477 }
478 $this->copyGifOntoGif($destImg, $cpImg, $conf, $workArea);
479 $this->ImageWrite($destImg, $theMask);
480 imageDestroy($cpImg);
481 imageDestroy($destImg);
482 // Mask the images
483 $this->ImageWrite($im, $theDest);
484 // Let combineExec handle maskNegation
485 $this->combineExec($theDest, $theImage, $theMask, $theDest, TRUE);
486 // The main image is loaded again...
487 $backIm = $this->imageCreateFromFile($theDest);
488 // ... and if nothing went wrong we load it onto the old one.
489 if ($backIm) {
490 if (!$this->saveAlphaLayer) {
491 ImageColorTransparent($backIm, -1);
492 }
493 $im = $backIm;
494 }
495 // Unlink files from process
496 if (!$this->dontUnlinkTempFiles) {
497 unlink($theDest);
498 unlink($theImage);
499 unlink($theMask);
500 }
501 }
502 }
503 }
504
505 /**
506 * Implements the "IMAGE" GIFBUILDER object, when the "mask" property is FALSE (using only $conf['file'])
507 *
508 * @param pointer $im GDlib image pointer
509 * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
510 * @param array $workArea The current working area coordinates.
511 * @return void
512 * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make(), maskImageOntoImage()
513 * @todo Define visibility
514 */
515 public function copyImageOntoImage(&$im, $conf, $workArea) {
516 if ($conf['file']) {
517 if (!\TYPO3\CMS\Core\Utility\GeneralUtility::inList($this->gdlibExtensions, $conf['BBOX'][2])) {
518 $conf['BBOX'] = $this->imageMagickConvert($conf['BBOX'][3], $this->gifExtension);
519 $conf['file'] = $conf['BBOX'][3];
520 }
521 $cpImg = $this->imageCreateFromFile($conf['file']);
522 $this->copyGifOntoGif($im, $cpImg, $conf, $workArea);
523 imageDestroy($cpImg);
524 }
525 }
526
527 /**
528 * Copies two GDlib image pointers onto each other, using TypoScript configuration from $conf and the input $workArea definition.
529 *
530 * @param pointer $im GDlib image pointer, destination (bottom image)
531 * @param pointer $cpImg GDlib image pointer, source (top image)
532 * @param array $conf TypoScript array with the properties for the IMAGE GIFBUILDER object. Only used for the "tile" property value.
533 * @param array $workArea Work area
534 * @return void Works on the $im image pointer
535 * @access private
536 * @todo Define visibility
537 */
538 public function copyGifOntoGif(&$im, $cpImg, $conf, $workArea) {
539 $cpW = imagesx($cpImg);
540 $cpH = imagesy($cpImg);
541 $tile = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $conf['tile']);
542 $tile[0] = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($tile[0], 1, 20);
543 $tile[1] = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($tile[1], 1, 20);
544 $cpOff = $this->objPosition($conf, $workArea, array($cpW * $tile[0], $cpH * $tile[1]));
545 for ($xt = 0; $xt < $tile[0]; $xt++) {
546 $Xstart = $cpOff[0] + $cpW * $xt;
547 // If this image is inside of the workArea, then go on
548 if ($Xstart + $cpW > $workArea[0]) {
549 // X:
550 if ($Xstart < $workArea[0]) {
551 $cpImgCutX = $workArea[0] - $Xstart;
552 $Xstart = $workArea[0];
553 } else {
554 $cpImgCutX = 0;
555 }
556 $w = $cpW - $cpImgCutX;
557 if ($Xstart > $workArea[0] + $workArea[2] - $w) {
558 $w = $workArea[0] + $workArea[2] - $Xstart;
559 }
560 // If this image is inside of the workArea, then go on
561 if ($Xstart < $workArea[0] + $workArea[2]) {
562 // Y:
563 for ($yt = 0; $yt < $tile[1]; $yt++) {
564 $Ystart = $cpOff[1] + $cpH * $yt;
565 // If this image is inside of the workArea, then go on
566 if ($Ystart + $cpH > $workArea[1]) {
567 if ($Ystart < $workArea[1]) {
568 $cpImgCutY = $workArea[1] - $Ystart;
569 $Ystart = $workArea[1];
570 } else {
571 $cpImgCutY = 0;
572 }
573 $h = $cpH - $cpImgCutY;
574 if ($Ystart > $workArea[1] + $workArea[3] - $h) {
575 $h = $workArea[1] + $workArea[3] - $Ystart;
576 }
577 // If this image is inside of the workArea, then go on
578 if ($Ystart < $workArea[1] + $workArea[3]) {
579 $this->imagecopyresized($im, $cpImg, $Xstart, $Ystart, $cpImgCutX, $cpImgCutY, $w, $h, $w, $h);
580 }
581 }
582 }
583 }
584 }
585 }
586 }
587
588 /**
589 * Alternative function for using the similar PHP function imagecopyresized(). Used for GD2 only.
590 *
591 * OK, the reason for this stupid fix is the following story:
592 * GD1.x was capable of copying two images together and combining their palettes! GD2 is apparently not.
593 * With GD2 only the palette of the dest-image is used which mostly results in totally black images when trying to
594 * copy a color-ful image onto the destination.
595 * The GD2-fix is to
596 * 1) Create a blank TRUE-COLOR image
597 * 2) Copy the destination image onto that one
598 * 3) Then do the actual operation; Copying the source (top image) onto that
599 * 4) ... and return the result pointer.
600 * 5) Reduce colors (if we do not, the result may become strange!)
601 * It works, but the resulting images is now a true-color PNG which may be very large.
602 * So, why not use 'imagetruecolortopalette ($im, TRUE, 256)' - well because it does NOT WORK! So simple is that.
603 *
604 * @param resource $dstImg Destination image
605 * @param resource $srcImg Source image
606 * @param integer $dstX Destination x-coordinate
607 * @param integer $dstY Destination y-coordinate
608 * @param integer $srcX Source x-coordinate
609 * @param integer $srcY Source y-coordinate
610 * @param integer $dstWidth Destination width
611 * @param integer $dstHeight Destination height
612 * @param integer $srcWidth Source width
613 * @param integer $srcHeight Source height
614 * @return void
615 * @access private
616 * @see t3lib_iconWorks::imagecopyresized()
617 * @todo Define visibility
618 */
619 public function imagecopyresized(&$dstImg, $srcImg, $dstX, $dstY, $srcX, $srcY, $dstWidth, $dstHeight, $srcWidth, $srcHeight) {
620 if ($this->imagecopyresized_fix && !$this->saveAlphaLayer) {
621 // Make true color image
622 $tmpImg = imagecreatetruecolor(imagesx($dstImg), imagesy($dstImg));
623 // Copy the source image onto that
624 imagecopyresized($tmpImg, $dstImg, 0, 0, 0, 0, imagesx($dstImg), imagesy($dstImg), imagesx($dstImg), imagesy($dstImg));
625 // Then copy the source image onto that (the actual operation!)
626 imagecopyresized($tmpImg, $srcImg, $dstX, $dstY, $srcX, $srcY, $dstWidth, $dstHeight, $srcWidth, $srcHeight);
627 // Set the destination image
628 $dstImg = $tmpImg;
629 } else {
630 imagecopyresized($dstImg, $srcImg, $dstX, $dstY, $srcX, $srcY, $dstWidth, $dstHeight, $srcWidth, $srcHeight);
631 }
632 }
633
634 /********************************
635 *
636 * Text / "TEXT" GIFBUILDER object
637 *
638 ********************************/
639 /**
640 * Implements the "TEXT" GIFBUILDER object
641 *
642 * @param pointer $im GDlib image pointer
643 * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
644 * @param array $workArea The current working area coordinates.
645 * @return void
646 * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
647 * @todo Define visibility
648 */
649 public function makeText(&$im, $conf, $workArea) {
650 // Spacing
651 list($spacing, $wordSpacing) = $this->calcWordSpacing($conf);
652 // Position
653 $txtPos = $this->txtPosition($conf, $workArea, $conf['BBOX']);
654 $theText = $this->recodeString($conf['text']);
655 if ($conf['imgMap'] && is_array($conf['imgMap.'])) {
656 $this->addToMap($this->calcTextCordsForMap($conf['BBOX'][2], $txtPos, $conf['imgMap.']), $conf['imgMap.']);
657 }
658 if (!$conf['hideButCreateMap']) {
659 // Font Color:
660 $cols = $this->convertColor($conf['fontColor']);
661 // NiceText is calculated
662 if (!$conf['niceText']) {
663 $Fcolor = ImageColorAllocate($im, $cols[0], $cols[1], $cols[2]);
664 // antiAliasing is setup:
665 $Fcolor = $conf['antiAlias'] ? $Fcolor : -$Fcolor;
666 for ($a = 0; $a < $conf['iterations']; $a++) {
667 // If any kind of spacing applys, we use this function:
668 if ($spacing || $wordSpacing) {
669 $this->SpacedImageTTFText($im, $conf['fontSize'], $conf['angle'], $txtPos[0], $txtPos[1], $Fcolor, self::prependAbsolutePath($conf['fontFile']), $theText, $spacing, $wordSpacing, $conf['splitRendering.']);
670 } else {
671 $this->renderTTFText($im, $conf['fontSize'], $conf['angle'], $txtPos[0], $txtPos[1], $Fcolor, $conf['fontFile'], $theText, $conf['splitRendering.'], $conf);
672 }
673 }
674 } else {
675 // NICETEXT::
676 // options anti_aliased and iterations is NOT available when doing this!!
677 $w = imagesx($im);
678 $h = imagesy($im);
679 $tmpStr = $this->randomName();
680 $fileMenu = $tmpStr . '_menuNT.' . $this->gifExtension;
681 $fileColor = $tmpStr . '_colorNT.' . $this->gifExtension;
682 $fileMask = $tmpStr . '_maskNT.' . $this->gifExtension;
683 // Scalefactor
684 $sF = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($conf['niceText.']['scaleFactor'], 2, 5);
685 $newW = ceil($sF * imagesx($im));
686 $newH = ceil($sF * imagesy($im));
687 // Make mask
688 $maskImg = imagecreatetruecolor($newW, $newH);
689 $Bcolor = ImageColorAllocate($maskImg, 255, 255, 255);
690 ImageFilledRectangle($maskImg, 0, 0, $newW, $newH, $Bcolor);
691 $Fcolor = ImageColorAllocate($maskImg, 0, 0, 0);
692 // If any kind of spacing applies, we use this function:
693 if ($spacing || $wordSpacing) {
694 $this->SpacedImageTTFText($maskImg, $conf['fontSize'], $conf['angle'], $txtPos[0], $txtPos[1], $Fcolor, self::prependAbsolutePath($conf['fontFile']), $theText, $spacing, $wordSpacing, $conf['splitRendering.'], $sF);
695 } else {
696 $this->renderTTFText($maskImg, $conf['fontSize'], $conf['angle'], $txtPos[0], $txtPos[1], $Fcolor, $conf['fontFile'], $theText, $conf['splitRendering.'], $conf, $sF);
697 }
698 $this->ImageWrite($maskImg, $fileMask);
699 ImageDestroy($maskImg);
700 // Downscales the mask
701 if ($this->NO_IM_EFFECTS) {
702 if ($this->maskNegate) {
703 // Negate 2 times makes no negate...
704 $command = trim($this->scalecmd . ' ' . $w . 'x' . $h . '!');
705 } else {
706 $command = trim($this->scalecmd . ' ' . $w . 'x' . $h . '! -negate');
707 }
708 } else {
709 if ($this->maskNegate) {
710 $command = trim($conf['niceText.']['before'] . ' ' . $this->scalecmd . ' ' . $w . 'x' . $h . '! ' . $conf['niceText.']['after']);
711 } else {
712 $command = trim($conf['niceText.']['before'] . ' ' . $this->scalecmd . ' ' . $w . 'x' . $h . '! ' . $conf['niceText.']['after'] . ' -negate');
713 }
714 if ($conf['niceText.']['sharpen']) {
715 if ($this->V5_EFFECTS) {
716 $command .= $this->v5_sharpen($conf['niceText.']['sharpen']);
717 } else {
718 $command .= ' -sharpen ' . \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($conf['niceText.']['sharpen'], 1, 99);
719 }
720 }
721 }
722 $this->imageMagickExec($fileMask, $fileMask, $command);
723 // Make the color-file
724 $colorImg = imagecreatetruecolor($w, $h);
725 $Ccolor = ImageColorAllocate($colorImg, $cols[0], $cols[1], $cols[2]);
726 ImageFilledRectangle($colorImg, 0, 0, $w, $h, $Ccolor);
727 $this->ImageWrite($colorImg, $fileColor);
728 ImageDestroy($colorImg);
729 // The mask is applied
730 // The main pictures is saved temporarily
731 $this->ImageWrite($im, $fileMenu);
732 $this->combineExec($fileMenu, $fileColor, $fileMask, $fileMenu);
733 // The main image is loaded again...
734 $backIm = $this->imageCreateFromFile($fileMenu);
735 // ... and if nothing went wrong we load it onto the old one.
736 if ($backIm) {
737 if (!$this->saveAlphaLayer) {
738 ImageColorTransparent($backIm, -1);
739 }
740 $im = $backIm;
741 }
742 // Deleting temporary files;
743 if (!$this->dontUnlinkTempFiles) {
744 unlink($fileMenu);
745 unlink($fileColor);
746 unlink($fileMask);
747 }
748 }
749 }
750 }
751
752 /**
753 * Calculates text position for printing the text onto the image based on configuration like alignment and workarea.
754 *
755 * @param array $conf TypoScript array for the TEXT GIFBUILDER object
756 * @param array $workArea Workarea definition
757 * @param array $BB Bounding box information, was set in \TYPO3\CMS\Frontend\Imaging\GifBuilder::start()
758 * @return array [0]=x, [1]=y, [2]=w, [3]=h
759 * @access private
760 * @see makeText()
761 * @todo Define visibility
762 */
763 public function txtPosition($conf, $workArea, $BB) {
764 $bbox = $BB[2];
765 $angle = intval($conf['angle']) / 180 * pi();
766 $conf['angle'] = 0;
767 $straightBB = $this->calcBBox($conf);
768 // offset, align, valign, workarea
769 // [0]=x, [1]=y, [2]=w, [3]=h
770 $result = array();
771 $result[2] = $BB[0];
772 $result[3] = $BB[1];
773 $w = $workArea[2];
774 $h = $workArea[3];
775 switch ($conf['align']) {
776 case 'right':
777
778 case 'center':
779 $factor = abs(cos($angle));
780 $sign = cos($angle) < 0 ? -1 : 1;
781 $len1 = $sign * $factor * $straightBB[0];
782 $len2 = $sign * $BB[0];
783 $result[0] = $w - ceil(($len2 * $factor + (1 - $factor) * $len1));
784 $factor = abs(sin($angle));
785 $sign = sin($angle) < 0 ? -1 : 1;
786 $len1 = $sign * $factor * $straightBB[0];
787 $len2 = $sign * $BB[1];
788 $result[1] = ceil($len2 * $factor + (1 - $factor) * $len1);
789 break;
790 }
791 switch ($conf['align']) {
792 case 'right':
793 break;
794 case 'center':
795 $result[0] = round($result[0] / 2);
796 $result[1] = round($result[1] / 2);
797 break;
798 default:
799 $result[0] = 0;
800 $result[1] = 0;
801 break;
802 }
803 $result = $this->applyOffset($result, \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $conf['offset']));
804 $result = $this->applyOffset($result, $workArea);
805 return $result;
806 }
807
808 /**
809 * Calculates bounding box information for the TEXT GIFBUILDER object.
810 *
811 * @param array $conf TypoScript array for the TEXT GIFBUILDER object
812 * @return array Array with three keys [0]/[1] being x/y and [2] being the bounding box array
813 * @access private
814 * @see txtPosition(), \TYPO3\CMS\Frontend\Imaging\GifBuilder::start()
815 * @todo Define visibility
816 */
817 public function calcBBox($conf) {
818 $sF = $this->getTextScalFactor($conf);
819 list($spacing, $wordSpacing) = $this->calcWordSpacing($conf, $sF);
820 $theText = $this->recodeString($conf['text']);
821 $charInf = $this->ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $theText, $conf['splitRendering.'], $sF);
822 $theBBoxInfo = $charInf;
823 if ($conf['angle']) {
824 $xArr = array($charInf[0], $charInf[2], $charInf[4], $charInf[6]);
825 $yArr = array($charInf[1], $charInf[3], $charInf[5], $charInf[7]);
826 $x = max($xArr) - min($xArr);
827 $y = max($yArr) - min($yArr);
828 } else {
829 $x = $charInf[2] - $charInf[0];
830 $y = $charInf[1] - $charInf[7];
831 }
832 // Set original lineHeight (used by line breaks):
833 $theBBoxInfo['lineHeight'] = $y;
834 // If any kind of spacing applys, we use this function:
835 if ($spacing || $wordSpacing) {
836 $x = 0;
837 if (!$spacing && $wordSpacing) {
838 $bits = explode(' ', $theText);
839 foreach ($bits as $word) {
840 $word .= ' ';
841 $wordInf = $this->ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $word, $conf['splitRendering.'], $sF);
842 $wordW = $wordInf[2] - $wordInf[0];
843 $x += $wordW + $wordSpacing;
844 }
845 } else {
846 $utf8Chars = $this->singleChars($theText);
847 // For each UTF-8 char, do:
848 foreach ($utf8Chars as $char) {
849 $charInf = $this->ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $char, $conf['splitRendering.'], $sF);
850 $charW = $charInf[2] - $charInf[0];
851 $x += $charW + ($char == ' ' ? $wordSpacing : $spacing);
852 }
853 }
854 } elseif (isset($conf['breakWidth']) && $conf['breakWidth'] && $this->getRenderedTextWidth($conf['text'], $conf) > $conf['breakWidth']) {
855 $maxWidth = 0;
856 $currentWidth = 0;
857 $breakWidth = $conf['breakWidth'];
858 $breakSpace = $this->getBreakSpace($conf, $theBBoxInfo);
859 $wordPairs = $this->getWordPairsForLineBreak($conf['text']);
860 // Iterate through all word pairs:
861 foreach ($wordPairs as $index => $wordPair) {
862 $wordWidth = $this->getRenderedTextWidth($wordPair, $conf);
863 if ($index == 0 || $currentWidth + $wordWidth <= $breakWidth) {
864 $currentWidth += $wordWidth;
865 } else {
866 $maxWidth = max($maxWidth, $currentWidth);
867 $y += $breakSpace;
868 // Restart:
869 $currentWidth = $wordWidth;
870 }
871 }
872 $x = max($maxWidth, $currentWidth) * $sF;
873 }
874 if ($sF > 1) {
875 $x = ceil($x / $sF);
876 $y = ceil($y / $sF);
877 if (is_array($theBBoxInfo)) {
878 foreach ($theBBoxInfo as &$value) {
879 $value = ceil($value / $sF);
880 }
881 unset($value);
882 }
883 }
884 return array($x, $y, $theBBoxInfo);
885 }
886
887 /**
888 * Adds an <area> tag to the internal variable $this->map which is used to accumulate the content for an ImageMap
889 *
890 * @param array $cords Coordinates for a polygon image map as created by ->calcTextCordsForMap()
891 * @param array $conf Configuration for "imgMap." property of a TEXT GIFBUILDER object.
892 * @return void
893 * @access private
894 * @see makeText(), calcTextCordsForMap()
895 * @todo Define visibility
896 */
897 public function addToMap($cords, $conf) {
898 $this->map .= '<area' . ' shape="poly"' . ' coords="' . implode(',', $cords) . '"' . ' href="' . htmlspecialchars($conf['url']) . '"' . ($conf['target'] ? ' target="' . htmlspecialchars($conf['target']) . '"' : '') . $JS . (strlen($conf['titleText']) ? ' title="' . htmlspecialchars($conf['titleText']) . '"' : '') . ' alt="' . htmlspecialchars($conf['altText']) . '" />';
899 }
900
901 /**
902 * Calculating the coordinates for a TEXT string on an image map. Used in an <area> tag
903 *
904 * @param array $cords Coordinates (from BBOX array)
905 * @param array $offset Offset array
906 * @param array $conf Configuration for "imgMap." property of a TEXT GIFBUILDER object.
907 * @return array
908 * @access private
909 * @see makeText(), calcTextCordsForMap()
910 * @todo Define visibility
911 */
912 public function calcTextCordsForMap($cords, $offset, $conf) {
913 $pars = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $conf['explode'] . ',');
914 $newCords[0] = $cords[0] + $offset[0] - $pars[0];
915 $newCords[1] = $cords[1] + $offset[1] + $pars[1];
916 $newCords[2] = $cords[2] + $offset[0] + $pars[0];
917 $newCords[3] = $cords[3] + $offset[1] + $pars[1];
918 $newCords[4] = $cords[4] + $offset[0] + $pars[0];
919 $newCords[5] = $cords[5] + $offset[1] - $pars[1];
920 $newCords[6] = $cords[6] + $offset[0] - $pars[0];
921 $newCords[7] = $cords[7] + $offset[1] - $pars[1];
922 return $newCords;
923 }
924
925 /**
926 * Printing text onto an image like the PHP function imageTTFText does but in addition it offers options for spacing of letters and words.
927 * Spacing is done by printing one char at a time and this means that the spacing is rather uneven and probably not very nice.
928 * See
929 *
930 * @param pointer $im (See argument for PHP function imageTTFtext())
931 * @param integer $fontSize (See argument for PHP function imageTTFtext())
932 * @param integer $angle (See argument for PHP function imageTTFtext())
933 * @param integer $x (See argument for PHP function imageTTFtext())
934 * @param integer $y (See argument for PHP function imageTTFtext())
935 * @param integer $Fcolor (See argument for PHP function imageTTFtext())
936 * @param string $fontFile (See argument for PHP function imageTTFtext())
937 * @param string $text (See argument for PHP function imageTTFtext()). UTF-8 string, possibly with entities in.
938 * @param integer $spacing The spacing of letters in pixels
939 * @param integer $wordSpacing The spacing of words in pixels
940 * @param array $splitRenderingConf Array
941 * @param integer $sF Scale factor
942 * @return void
943 * @access private
944 * @todo Define visibility
945 */
946 public function SpacedImageTTFText(&$im, $fontSize, $angle, $x, $y, $Fcolor, $fontFile, $text, $spacing, $wordSpacing, $splitRenderingConf, $sF = 1) {
947 $spacing *= $sF;
948 $wordSpacing *= $sF;
949 if (!$spacing && $wordSpacing) {
950 $bits = explode(' ', $text);
951 foreach ($bits as $word) {
952 $word .= ' ';
953 $wordInf = $this->ImageTTFBBoxWrapper($fontSize, $angle, $fontFile, $word, $splitRenderingConf, $sF);
954 $wordW = $wordInf[2] - $wordInf[0];
955 $this->ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $Fcolor, $fontFile, $word, $splitRenderingConf, $sF);
956 $x += $wordW + $wordSpacing;
957 }
958 } else {
959 $utf8Chars = $this->singleChars($text);
960 // For each UTF-8 char, do:
961 foreach ($utf8Chars as $char) {
962 $charInf = $this->ImageTTFBBoxWrapper($fontSize, $angle, $fontFile, $char, $splitRenderingConf, $sF);
963 $charW = $charInf[2] - $charInf[0];
964 $this->ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $Fcolor, $fontFile, $char, $splitRenderingConf, $sF);
965 $x += $charW + ($char == ' ' ? $wordSpacing : $spacing);
966 }
967 }
968 }
969
970 /**
971 * Function that finds the right fontsize that will render the textstring within a certain width
972 *
973 * @param array $conf The TypoScript properties of the TEXT GIFBUILDER object
974 * @return integer The new fontSize
975 * @access private
976 * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::start()
977 * @todo Define visibility
978 */
979 public function fontResize($conf) {
980 // You have to use +calc options like [10.h] in 'offset' to get the right position of your text-image, if you use +calc in XY height!!!!
981 $maxWidth = intval($conf['maxWidth']);
982 list($spacing, $wordSpacing) = $this->calcWordSpacing($conf);
983 if ($maxWidth) {
984 // If any kind of spacing applys, we use this function:
985 if ($spacing || $wordSpacing) {
986 return $conf['fontSize'];
987 } else {
988 do {
989 // Determine bounding box.
990 $bounds = $this->ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $this->recodeString($conf['text']), $conf['splitRendering.']);
991 if ($conf['angle'] < 0) {
992 $pixelWidth = abs($bounds[4] - $bounds[0]);
993 } elseif ($conf['angle'] > 0) {
994 $pixelWidth = abs($bounds[2] - $bounds[6]);
995 } else {
996 $pixelWidth = abs($bounds[4] - $bounds[6]);
997 }
998 // Size is fine, exit:
999 if ($pixelWidth <= $maxWidth) {
1000 break;
1001 } else {
1002 $conf['fontSize']--;
1003 }
1004 } while ($conf['fontSize'] > 1);
1005 }
1006 }
1007 return $conf['fontSize'];
1008 }
1009
1010 /**
1011 * Wrapper for ImageTTFBBox
1012 *
1013 * @param integer $fontSize (See argument for PHP function ImageTTFBBox())
1014 * @param integer $angle (See argument for PHP function ImageTTFBBox())
1015 * @param string $fontFile (See argument for PHP function ImageTTFBBox())
1016 * @param string $string (See argument for PHP function ImageTTFBBox())
1017 * @param array $splitRendering Split-rendering configuration
1018 * @param integer $sF Scale factor
1019 * @return array Information array.
1020 * @todo Define visibility
1021 */
1022 public function ImageTTFBBoxWrapper($fontSize, $angle, $fontFile, $string, $splitRendering, $sF = 1) {
1023 // Initialize:
1024 $offsetInfo = array();
1025 $stringParts = $this->splitString($string, $splitRendering, $fontSize, $fontFile);
1026 // Traverse string parts:
1027 foreach ($stringParts as $strCfg) {
1028 $fontFile = self::prependAbsolutePath($strCfg['fontFile']);
1029 if (is_readable($fontFile)) {
1030 /**
1031 * Calculate Bounding Box for part.
1032 * Due to a PHP bug, we must retry if $calc[2] is negative.
1033 *
1034 * @see https://bugs.php.net/bug.php?id=51315
1035 * @see https://bugs.php.net/bug.php?id=22513
1036 */
1037 $try = 0;
1038 do {
1039 $calc = ImageTTFBBox(\TYPO3\CMS\Core\Utility\GeneralUtility::freetypeDpiComp($sF * $strCfg['fontSize']), $angle, $fontFile, $strCfg['str']);
1040 } while ($calc[2] < 0 && $try++ < 10);
1041 // Calculate offsets:
1042 if (!count($offsetInfo)) {
1043 // First run, just copy over.
1044 $offsetInfo = $calc;
1045 } else {
1046 $offsetInfo[2] += $calc[2] - $calc[0] + intval($splitRendering['compX']) + intval($strCfg['xSpaceBefore']) + intval($strCfg['xSpaceAfter']);
1047 $offsetInfo[3] += $calc[3] - $calc[1] - intval($splitRendering['compY']) - intval($strCfg['ySpaceBefore']) - intval($strCfg['ySpaceAfter']);
1048 $offsetInfo[4] += $calc[4] - $calc[6] + intval($splitRendering['compX']) + intval($strCfg['xSpaceBefore']) + intval($strCfg['xSpaceAfter']);
1049 $offsetInfo[5] += $calc[5] - $calc[7] - intval($splitRendering['compY']) - intval($strCfg['ySpaceBefore']) - intval($strCfg['ySpaceAfter']);
1050 }
1051 } else {
1052 debug('cannot read file: ' . $fontFile, 'TYPO3\\CMS\\Core\\Imaging\\GraphicalFunctions::ImageTTFBBoxWrapper()');
1053 }
1054 }
1055 return $offsetInfo;
1056 }
1057
1058 /**
1059 * Wrapper for ImageTTFText
1060 *
1061 * @param pointer $im (See argument for PHP function imageTTFtext())
1062 * @param integer $fontSize (See argument for PHP function imageTTFtext())
1063 * @param integer $angle (See argument for PHP function imageTTFtext())
1064 * @param integer $x (See argument for PHP function imageTTFtext())
1065 * @param integer $y (See argument for PHP function imageTTFtext())
1066 * @param integer $color (See argument for PHP function imageTTFtext())
1067 * @param string $fontFile (See argument for PHP function imageTTFtext())
1068 * @param string $string (See argument for PHP function imageTTFtext()). UTF-8 string, possibly with entities in.
1069 * @param array $splitRendering Split-rendering configuration
1070 * @param integer $sF Scale factor
1071 * @return void
1072 * @todo Define visibility
1073 */
1074 public function ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $color, $fontFile, $string, $splitRendering, $sF = 1) {
1075 // Initialize:
1076 $stringParts = $this->splitString($string, $splitRendering, $fontSize, $fontFile);
1077 $x = ceil($sF * $x);
1078 $y = ceil($sF * $y);
1079 // Traverse string parts:
1080 foreach ($stringParts as $i => $strCfg) {
1081 // Initialize:
1082 $colorIndex = $color;
1083 // Set custom color if any (only when niceText is off):
1084 if ($strCfg['color'] && $sF == 1) {
1085 $cols = $this->convertColor($strCfg['color']);
1086 $colorIndex = ImageColorAllocate($im, $cols[0], $cols[1], $cols[2]);
1087 $colorIndex = $color >= 0 ? $colorIndex : -$colorIndex;
1088 }
1089 // Setting xSpaceBefore
1090 if ($i) {
1091 $x += intval($strCfg['xSpaceBefore']);
1092 $y -= intval($strCfg['ySpaceBefore']);
1093 }
1094 $fontFile = self::prependAbsolutePath($strCfg['fontFile']);
1095 if (is_readable($fontFile)) {
1096 // Render part:
1097 ImageTTFText($im, \TYPO3\CMS\Core\Utility\GeneralUtility::freetypeDpiComp($sF * $strCfg['fontSize']), $angle, $x, $y, $colorIndex, $fontFile, $strCfg['str']);
1098 // Calculate offset to apply:
1099 $wordInf = ImageTTFBBox(\TYPO3\CMS\Core\Utility\GeneralUtility::freetypeDpiComp($sF * $strCfg['fontSize']), $angle, self::prependAbsolutePath($strCfg['fontFile']), $strCfg['str']);
1100 $x += $wordInf[2] - $wordInf[0] + intval($splitRendering['compX']) + intval($strCfg['xSpaceAfter']);
1101 $y += $wordInf[5] - $wordInf[7] - intval($splitRendering['compY']) - intval($strCfg['ySpaceAfter']);
1102 } else {
1103 debug('cannot read file: ' . $fontFile, 'TYPO3\\CMS\\Core\\Imaging\\GraphicalFunctions::ImageTTFTextWrapper()');
1104 }
1105 }
1106 }
1107
1108 /**
1109 * Splitting a string for ImageTTFBBox up into an array where each part has its own configuration options.
1110 *
1111 * @param string $string UTF-8 string
1112 * @param array $splitRendering Split-rendering configuration from GIFBUILDER TEXT object.
1113 * @param integer $fontSize Current fontsize
1114 * @param string $fontFile Current font file
1115 * @return array Array with input string splitted according to configuration
1116 * @todo Define visibility
1117 */
1118 public function splitString($string, $splitRendering, $fontSize, $fontFile) {
1119 // Initialize by setting the whole string and default configuration as the first entry.
1120 $result = array();
1121 $result[] = array(
1122 'str' => $string,
1123 'fontSize' => $fontSize,
1124 'fontFile' => $fontFile
1125 );
1126 // Traverse the split-rendering configuration:
1127 // Splitting will create more entries in $result with individual configurations.
1128 if (is_array($splitRendering)) {
1129 $sKeyArray = \TYPO3\CMS\Core\TypoScript\TemplateService::sortedKeyList($splitRendering);
1130 // Traverse configured options:
1131 foreach ($sKeyArray as $key) {
1132 $cfg = $splitRendering[$key . '.'];
1133 // Process each type of split rendering keyword:
1134 switch ((string) $splitRendering[$key]) {
1135 case 'highlightWord':
1136 if (strlen($cfg['value'])) {
1137 $newResult = array();
1138 // Traverse the current parts of the result array:
1139 foreach ($result as $part) {
1140 // Explode the string value by the word value to highlight:
1141 $explodedParts = explode($cfg['value'], $part['str']);
1142 foreach ($explodedParts as $c => $expValue) {
1143 if (strlen($expValue)) {
1144 $newResult[] = array_merge($part, array('str' => $expValue));
1145 }
1146 if ($c + 1 < count($explodedParts)) {
1147 $newResult[] = array(
1148 'str' => $cfg['value'],
1149 'fontSize' => $cfg['fontSize'] ? $cfg['fontSize'] : $part['fontSize'],
1150 'fontFile' => $cfg['fontFile'] ? $cfg['fontFile'] : $part['fontFile'],
1151 'color' => $cfg['color'],
1152 'xSpaceBefore' => $cfg['xSpaceBefore'],
1153 'xSpaceAfter' => $cfg['xSpaceAfter'],
1154 'ySpaceBefore' => $cfg['ySpaceBefore'],
1155 'ySpaceAfter' => $cfg['ySpaceAfter']
1156 );
1157 }
1158 }
1159 }
1160 // Set the new result as result array:
1161 if (count($newResult)) {
1162 $result = $newResult;
1163 }
1164 }
1165 break;
1166 case 'charRange':
1167 if (strlen($cfg['value'])) {
1168 // Initialize range:
1169 $ranges = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(',', $cfg['value'], 1);
1170 foreach ($ranges as $i => $rangeDef) {
1171 $ranges[$i] = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode('-', $ranges[$i]);
1172 if (!isset($ranges[$i][1])) {
1173 $ranges[$i][1] = $ranges[$i][0];
1174 }
1175 }
1176 $newResult = array();
1177 // Traverse the current parts of the result array:
1178 foreach ($result as $part) {
1179 // Initialize:
1180 $currentState = -1;
1181 $bankAccum = '';
1182 // Explode the string value by the word value to highlight:
1183 $utf8Chars = $this->singleChars($part['str']);
1184 foreach ($utf8Chars as $utfChar) {
1185 // Find number and evaluate position:
1186 $uNumber = $this->csConvObj->utf8CharToUnumber($utfChar);
1187 $inRange = 0;
1188 foreach ($ranges as $rangeDef) {
1189 if ($uNumber >= $rangeDef[0] && (!$rangeDef[1] || $uNumber <= $rangeDef[1])) {
1190 $inRange = 1;
1191 break;
1192 }
1193 }
1194 if ($currentState == -1) {
1195 $currentState = $inRange;
1196 }
1197 // Initialize first char
1198 // Switch bank:
1199 if ($inRange != $currentState && !\TYPO3\CMS\Core\Utility\GeneralUtility::inList('32,10,13,9', $uNumber)) {
1200 // Set result:
1201 if (strlen($bankAccum)) {
1202 $newResult[] = array(
1203 'str' => $bankAccum,
1204 'fontSize' => $currentState && $cfg['fontSize'] ? $cfg['fontSize'] : $part['fontSize'],
1205 'fontFile' => $currentState && $cfg['fontFile'] ? $cfg['fontFile'] : $part['fontFile'],
1206 'color' => $currentState ? $cfg['color'] : '',
1207 'xSpaceBefore' => $currentState ? $cfg['xSpaceBefore'] : '',
1208 'xSpaceAfter' => $currentState ? $cfg['xSpaceAfter'] : '',
1209 'ySpaceBefore' => $currentState ? $cfg['ySpaceBefore'] : '',
1210 'ySpaceAfter' => $currentState ? $cfg['ySpaceAfter'] : ''
1211 );
1212 }
1213 // Initialize new settings:
1214 $currentState = $inRange;
1215 $bankAccum = '';
1216 }
1217 // Add char to bank:
1218 $bankAccum .= $utfChar;
1219 }
1220 // Set result for FINAL part:
1221 if (strlen($bankAccum)) {
1222 $newResult[] = array(
1223 'str' => $bankAccum,
1224 'fontSize' => $currentState && $cfg['fontSize'] ? $cfg['fontSize'] : $part['fontSize'],
1225 'fontFile' => $currentState && $cfg['fontFile'] ? $cfg['fontFile'] : $part['fontFile'],
1226 'color' => $currentState ? $cfg['color'] : '',
1227 'xSpaceBefore' => $currentState ? $cfg['xSpaceBefore'] : '',
1228 'xSpaceAfter' => $currentState ? $cfg['xSpaceAfter'] : '',
1229 'ySpaceBefore' => $currentState ? $cfg['ySpaceBefore'] : '',
1230 'ySpaceAfter' => $currentState ? $cfg['ySpaceAfter'] : ''
1231 );
1232 }
1233 }
1234 // Set the new result as result array:
1235 if (count($newResult)) {
1236 $result = $newResult;
1237 }
1238 }
1239 break;
1240 }
1241 }
1242 }
1243 return $result;
1244 }
1245
1246 /**
1247 * Calculates the spacing and wordSpacing values
1248 *
1249 * @param array $conf TypoScript array for the TEXT GIFBUILDER object
1250 * @param integer $scaleFactor TypoScript value from eg $conf['niceText.']['scaleFactor']
1251 * @return array Array with two keys [0]/[1] being array($spacing,$wordSpacing)
1252 * @access private
1253 * @see calcBBox()
1254 * @todo Define visibility
1255 */
1256 public function calcWordSpacing($conf, $scaleFactor = 1) {
1257 $spacing = intval($conf['spacing']);
1258 $wordSpacing = intval($conf['wordSpacing']);
1259 $wordSpacing = $wordSpacing ? $wordSpacing : $spacing * 2;
1260 $spacing *= $scaleFactor;
1261 $wordSpacing *= $scaleFactor;
1262 return array($spacing, $wordSpacing);
1263 }
1264
1265 /**
1266 * Calculates and returns the niceText.scaleFactor
1267 *
1268 * @param array $conf TypoScript array for the TEXT GIFBUILDER object
1269 * @return integer TypoScript value from eg $conf['niceText.']['scaleFactor']
1270 * @access private
1271 * @todo Define visibility
1272 */
1273 public function getTextScalFactor($conf) {
1274 if (!$conf['niceText']) {
1275 $sF = 1;
1276 } else {
1277 // NICETEXT::
1278 $sF = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($conf['niceText.']['scaleFactor'], 2, 5);
1279 }
1280 return $sF;
1281 }
1282
1283 /**
1284 * Renders a regular text and takes care of a possible line break automatically.
1285 *
1286 * @param pointer $im (See argument for PHP function imageTTFtext())
1287 * @param integer $fontSize (See argument for PHP function imageTTFtext())
1288 * @param integer $angle (See argument for PHP function imageTTFtext())
1289 * @param integer $x (See argument for PHP function imageTTFtext())
1290 * @param integer $y (See argument for PHP function imageTTFtext())
1291 * @param integer $color (See argument for PHP function imageTTFtext())
1292 * @param string $fontFile (See argument for PHP function imageTTFtext())
1293 * @param string $string (See argument for PHP function imageTTFtext()). UTF-8 string, possibly with entities in.
1294 * @param array $splitRendering Split-rendering configuration
1295 * @param array $conf The configuration
1296 * @param integer $sF Scale factor
1297 * @return void
1298 */
1299 protected function renderTTFText(&$im, $fontSize, $angle, $x, $y, $color, $fontFile, $string, $splitRendering, $conf, $sF = 1) {
1300 if (isset($conf['breakWidth']) && $conf['breakWidth'] && $this->getRenderedTextWidth($string, $conf) > $conf['breakWidth']) {
1301 $phrase = '';
1302 $currentWidth = 0;
1303 $breakWidth = $conf['breakWidth'];
1304 $breakSpace = $this->getBreakSpace($conf);
1305 $wordPairs = $this->getWordPairsForLineBreak($string);
1306 // Iterate through all word pairs:
1307 foreach ($wordPairs as $index => $wordPair) {
1308 $wordWidth = $this->getRenderedTextWidth($wordPair, $conf);
1309 if ($index == 0 || $currentWidth + $wordWidth <= $breakWidth) {
1310 $currentWidth += $wordWidth;
1311 $phrase .= $wordPair;
1312 } else {
1313 // Render the current phrase that is below breakWidth:
1314 $this->ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $color, $fontFile, $phrase, $splitRendering, $sF);
1315 // Calculate the news height offset:
1316 $y += $breakSpace;
1317 // Restart the phrase:
1318 $currentWidth = $wordWidth;
1319 $phrase = $wordPair;
1320 }
1321 }
1322 // Render the remaining phrase:
1323 if ($currentWidth) {
1324 $this->ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $color, $fontFile, $phrase, $splitRendering, $sF);
1325 }
1326 } else {
1327 $this->ImageTTFTextWrapper($im, $fontSize, $angle, $x, $y, $color, $fontFile, $string, $splitRendering, $sF);
1328 }
1329 }
1330
1331 /**
1332 * Gets the word pairs used for automatic line breaks.
1333 *
1334 * @param string $string
1335 * @return array
1336 */
1337 protected function getWordPairsForLineBreak($string) {
1338 $wordPairs = array();
1339 $wordsArray = preg_split('#([- .,!:]+)#', $string, -1, PREG_SPLIT_DELIM_CAPTURE);
1340 $wordsCount = count($wordsArray);
1341 for ($index = 0; $index < $wordsCount; $index += 2) {
1342 $wordPairs[] = $wordsArray[$index] . $wordsArray[($index + 1)];
1343 }
1344 return $wordPairs;
1345 }
1346
1347 /**
1348 * Gets the rendered text width.
1349 *
1350 * @param string $text
1351 * @param array $conf
1352 * @param integer
1353 */
1354 protected function getRenderedTextWidth($text, $conf) {
1355 $bounds = $this->ImageTTFBBoxWrapper($conf['fontSize'], $conf['angle'], $conf['fontFile'], $this->recodeString($text), $conf['splitRendering.']);
1356 if ($conf['angle'] < 0) {
1357 $pixelWidth = abs($bounds[4] - $bounds[0]);
1358 } elseif ($conf['angle'] > 0) {
1359 $pixelWidth = abs($bounds[2] - $bounds[6]);
1360 } else {
1361 $pixelWidth = abs($bounds[4] - $bounds[6]);
1362 }
1363 return $pixelWidth;
1364 }
1365
1366 /**
1367 * Gets the break space for each new line.
1368 *
1369 * @param array $conf TypoScript configuration for the currently rendered object
1370 * @param array $boundingBox The bounding box the the currently rendered object
1371 * @return integer The break space
1372 */
1373 protected function getBreakSpace($conf, array $boundingBox = NULL) {
1374 if (!isset($boundingBox)) {
1375 $boundingBox = $this->calcBBox($conf);
1376 $boundingBox = $boundingBox[2];
1377 }
1378 if (isset($conf['breakSpace']) && $conf['breakSpace']) {
1379 $breakSpace = $boundingBox['lineHeight'] * $conf['breakSpace'];
1380 } else {
1381 $breakSpace = $boundingBox['lineHeight'];
1382 }
1383 return $breakSpace;
1384 }
1385
1386 /*********************************************
1387 *
1388 * Other GIFBUILDER objects related to TEXT
1389 *
1390 *********************************************/
1391 /**
1392 * Implements the "OUTLINE" GIFBUILDER object / property for the TEXT object
1393 *
1394 * @param pointer $im GDlib image pointer
1395 * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1396 * @param array $workArea The current working area coordinates.
1397 * @param array $txtConf TypoScript array with configuration for the associated TEXT GIFBUILDER object.
1398 * @return void
1399 * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make(), makeText()
1400 * @todo Define visibility
1401 */
1402 public function makeOutline(&$im, $conf, $workArea, $txtConf) {
1403 $thickness = intval($conf['thickness']);
1404 if ($thickness) {
1405 $txtConf['fontColor'] = $conf['color'];
1406 $outLineDist = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($thickness, 1, 2);
1407 for ($b = 1; $b <= $outLineDist; $b++) {
1408 if ($b == 1) {
1409 $it = 8;
1410 } else {
1411 $it = 16;
1412 }
1413 $outL = $this->circleOffset($b, $it);
1414 for ($a = 0; $a < $it; $a++) {
1415 $this->makeText($im, $txtConf, $this->applyOffset($workArea, $outL[$a]));
1416 }
1417 }
1418 }
1419 }
1420
1421 /**
1422 * Creates some offset values in an array used to simulate a circularly applied outline around TEXT
1423 *
1424 * access private
1425 *
1426 * @param integer $distance Distance
1427 * @param integer $iterations Iterations.
1428 * @return array
1429 * @see makeOutline()
1430 * @todo Define visibility
1431 */
1432 public function circleOffset($distance, $iterations) {
1433 $res = array();
1434 if ($distance && $iterations) {
1435 for ($a = 0; $a < $iterations; $a++) {
1436 $yOff = round(sin((2 * pi() / $iterations * ($a + 1))) * 100 * $distance);
1437 if ($yOff) {
1438 $yOff = intval(ceil(abs(($yOff / 100))) * ($yOff / abs($yOff)));
1439 }
1440 $xOff = round(cos((2 * pi() / $iterations * ($a + 1))) * 100 * $distance);
1441 if ($xOff) {
1442 $xOff = intval(ceil(abs(($xOff / 100))) * ($xOff / abs($xOff)));
1443 }
1444 $res[$a] = array($xOff, $yOff);
1445 }
1446 }
1447 return $res;
1448 }
1449
1450 /**
1451 * Implements the "EMBOSS" GIFBUILDER object / property for the TEXT object
1452 *
1453 * @param pointer $im GDlib image pointer
1454 * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1455 * @param array $workArea The current working area coordinates.
1456 * @param array $txtConf TypoScript array with configuration for the associated TEXT GIFBUILDER object.
1457 * @return void
1458 * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make(), makeShadow()
1459 * @todo Define visibility
1460 */
1461 public function makeEmboss(&$im, $conf, $workArea, $txtConf) {
1462 $conf['color'] = $conf['highColor'];
1463 $this->makeShadow($im, $conf, $workArea, $txtConf);
1464 $newOffset = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $conf['offset']);
1465 $newOffset[0] *= -1;
1466 $newOffset[1] *= -1;
1467 $conf['offset'] = implode(',', $newOffset);
1468 $conf['color'] = $conf['lowColor'];
1469 $this->makeShadow($im, $conf, $workArea, $txtConf);
1470 }
1471
1472 /**
1473 * Implements the "SHADOW" GIFBUILDER object / property for the TEXT object
1474 * The operation involves ImageMagick for combining.
1475 *
1476 * @param pointer $im GDlib image pointer
1477 * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1478 * @param array $workArea The current working area coordinates.
1479 * @param array $txtConf TypoScript array with configuration for the associated TEXT GIFBUILDER object.
1480 * @retur void
1481 * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make(), makeText(), makeEmboss()
1482 * @todo Define visibility
1483 */
1484 public function makeShadow(&$im, $conf, $workArea, $txtConf) {
1485 $workArea = $this->applyOffset($workArea, \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $conf['offset']));
1486 $blurRate = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange(intval($conf['blur']), 0, 99);
1487 // No effects if ImageMagick ver. 5+
1488 if (!$blurRate || $this->NO_IM_EFFECTS) {
1489 $txtConf['fontColor'] = $conf['color'];
1490 $this->makeText($im, $txtConf, $workArea);
1491 } else {
1492 $w = imagesx($im);
1493 $h = imagesy($im);
1494 // Area around the blur used for cropping something
1495 $blurBorder = 3;
1496 $tmpStr = $this->randomName();
1497 $fileMenu = $tmpStr . '_menu.' . $this->gifExtension;
1498 $fileColor = $tmpStr . '_color.' . $this->gifExtension;
1499 $fileMask = $tmpStr . '_mask.' . $this->gifExtension;
1500 // BlurColor Image laves
1501 $blurColImg = imagecreatetruecolor($w, $h);
1502 $bcols = $this->convertColor($conf['color']);
1503 $Bcolor = ImageColorAllocate($blurColImg, $bcols[0], $bcols[1], $bcols[2]);
1504 ImageFilledRectangle($blurColImg, 0, 0, $w, $h, $Bcolor);
1505 $this->ImageWrite($blurColImg, $fileColor);
1506 ImageDestroy($blurColImg);
1507 // The mask is made: BlurTextImage
1508 $blurTextImg = imagecreatetruecolor($w + $blurBorder * 2, $h + $blurBorder * 2);
1509 // Black background
1510 $Bcolor = ImageColorAllocate($blurTextImg, 0, 0, 0);
1511 ImageFilledRectangle($blurTextImg, 0, 0, $w + $blurBorder * 2, $h + $blurBorder * 2, $Bcolor);
1512 $txtConf['fontColor'] = 'white';
1513 $blurBordArr = array($blurBorder, $blurBorder);
1514 $this->makeText($blurTextImg, $txtConf, $this->applyOffset($workArea, $blurBordArr));
1515 // Dump to temporary file
1516 $this->ImageWrite($blurTextImg, $fileMask);
1517 // Destroy
1518 ImageDestroy($blurTextImg);
1519 $command = '';
1520 $command .= $this->maskNegate;
1521 if ($this->V5_EFFECTS) {
1522 $command .= $this->v5_blur($blurRate + 1);
1523 } else {
1524 // Blurring of the mask
1525 // How many blur-commands that is executed. Min = 1;
1526 $times = ceil($blurRate / 10);
1527 // Here I boost the blur-rate so that it is 100 already at 25. The rest is done by up to 99 iterations of the blur-command.
1528 $newBlurRate = $blurRate * 4;
1529 $newBlurRate = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($newBlurRate, 1, 99);
1530 // Building blur-command
1531 for ($a = 0; $a < $times; $a++) {
1532 $command .= ' -blur ' . $blurRate;
1533 }
1534 }
1535 $this->imageMagickExec($fileMask, $fileMask, $command . ' +matte');
1536 // The mask is loaded again
1537 $blurTextImg_tmp = $this->imageCreateFromFile($fileMask);
1538 // If nothing went wrong we continue with the blurred mask
1539 if ($blurTextImg_tmp) {
1540 // Cropping the border from the mask
1541 $blurTextImg = imagecreatetruecolor($w, $h);
1542 $this->imagecopyresized($blurTextImg, $blurTextImg_tmp, 0, 0, $blurBorder, $blurBorder, $w, $h, $w, $h);
1543 // Destroy the temporary mask
1544 ImageDestroy($blurTextImg_tmp);
1545 // Adjust the mask
1546 $intensity = 40;
1547 if ($conf['intensity']) {
1548 $intensity = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($conf['intensity'], 0, 100);
1549 }
1550 $intensity = ceil(255 - $intensity / 100 * 255);
1551 $this->inputLevels($blurTextImg, 0, $intensity, $this->maskNegate);
1552 $opacity = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange(intval($conf['opacity']), 0, 100);
1553 if ($opacity && $opacity < 100) {
1554 $high = ceil(255 * $opacity / 100);
1555 // Reducing levels as the opacity demands
1556 $this->outputLevels($blurTextImg, 0, $high, $this->maskNegate);
1557 }
1558 // Dump the mask again
1559 $this->ImageWrite($blurTextImg, $fileMask);
1560 // Destroy the mask
1561 ImageDestroy($blurTextImg);
1562 // The pictures are combined
1563 // The main pictures is saved temporarily
1564 $this->ImageWrite($im, $fileMenu);
1565 $this->combineExec($fileMenu, $fileColor, $fileMask, $fileMenu);
1566 // The main image is loaded again...
1567 $backIm = $this->imageCreateFromFile($fileMenu);
1568 // ... and if nothing went wrong we load it onto the old one.
1569 if ($backIm) {
1570 if (!$this->saveAlphaLayer) {
1571 ImageColorTransparent($backIm, -1);
1572 }
1573 $im = $backIm;
1574 }
1575 }
1576 // Deleting temporary files;
1577 if (!$this->dontUnlinkTempFiles) {
1578 unlink($fileMenu);
1579 unlink($fileColor);
1580 unlink($fileMask);
1581 }
1582 }
1583 }
1584
1585 /****************************
1586 *
1587 * Other GIFBUILDER objects
1588 *
1589 ****************************/
1590 /**
1591 * Implements the "BOX" GIFBUILDER object
1592 *
1593 * @param pointer $im GDlib image pointer
1594 * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1595 * @param array $workArea The current working area coordinates.
1596 * @return void
1597 * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
1598 * @todo Define visibility
1599 */
1600 public function makeBox(&$im, $conf, $workArea) {
1601 $cords = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $conf['dimensions'] . ',,,');
1602 $conf['offset'] = $cords[0] . ',' . $cords[1];
1603 $cords = $this->objPosition($conf, $workArea, array($cords[2], $cords[3]));
1604 $cols = $this->convertColor($conf['color']);
1605 $opacity = 0;
1606 if (isset($conf['opacity'])) {
1607 // conversion:
1608 // PHP 0 = opaque, 127 = transparent
1609 // TYPO3 100 = opaque, 0 = transparent
1610 $opacity = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange(intval($conf['opacity']), 1, 100, 1);
1611 $opacity = abs($opacity - 100);
1612 $opacity = round(127 * $opacity / 100);
1613 }
1614 $tmpColor = ImageColorAllocateAlpha($im, $cols[0], $cols[1], $cols[2], $opacity);
1615 imagefilledrectangle($im, $cords[0], $cords[1], $cords[0] + $cords[2] - 1, $cords[1] + $cords[3] - 1, $tmpColor);
1616 }
1617
1618 /**
1619 * Implements the "Ellipse" GIFBUILDER object
1620 * Example Typoscript:
1621 * file = GIFBUILDER
1622 * file {
1623 * XY = 200,200
1624 * format = jpg
1625 * quality = 100
1626 * 10 = ELLIPSE
1627 * 10.dimensions = 100,100,50,50
1628 * 10.color = red
1629 *
1630 * $workArea = X,Y
1631 * $conf['dimensions'] = offset x, offset y, width of ellipse, height of ellipse
1632 *
1633 * @param pointer $im GDlib image pointer
1634 * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1635 * @param array $workArea The current working area coordinates.
1636 * @return void
1637 * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
1638 */
1639 public function makeEllipse(&$im, array $conf, array $workArea) {
1640 $ellipseConfiguration = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $conf['dimensions'] . ',,,');
1641 // Ellipse offset inside workArea (x/y)
1642 $conf['offset'] = $ellipseConfiguration[0] . ',' . $ellipseConfiguration[1];
1643 // @see objPosition
1644 $imageCoordinates = $this->objPosition($conf, $workArea, array($ellipseConfiguration[2], $ellipseConfiguration[3]));
1645 $color = $this->convertColor($conf['color']);
1646 $fillingColor = imagecolorallocate($im, $color[0], $color[1], $color[2]);
1647 imagefilledellipse($im, $imageCoordinates[0], $imageCoordinates[1], $imageCoordinates[2], $imageCoordinates[3], $fillingColor);
1648 }
1649
1650 /**
1651 * Implements the "EFFECT" GIFBUILDER object
1652 * The operation involves ImageMagick for applying effects
1653 *
1654 * @param pointer $im GDlib image pointer
1655 * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1656 * @return void
1657 * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make(), applyImageMagickToPHPGif()
1658 * @todo Define visibility
1659 */
1660 public function makeEffect(&$im, $conf) {
1661 $commands = $this->IMparams($conf['value']);
1662 if ($commands) {
1663 $this->applyImageMagickToPHPGif($im, $commands);
1664 }
1665 }
1666
1667 /**
1668 * Creating ImageMagick paramters from TypoScript property
1669 *
1670 * @param string $setup A string with effect keywords=value pairs separated by "|
1671 * @return string ImageMagick prepared parameters.
1672 * @access private
1673 * @see makeEffect()
1674 * @todo Define visibility
1675 */
1676 public function IMparams($setup) {
1677 if (!trim($setup)) {
1678 return '';
1679 }
1680 $effects = explode('|', $setup);
1681 $commands = '';
1682 foreach ($effects as $val) {
1683 $pairs = explode('=', $val, 2);
1684 $value = trim($pairs[1]);
1685 $effect = strtolower(trim($pairs[0]));
1686 switch ($effect) {
1687 case 'gamma':
1688 $commands .= ' -gamma ' . doubleval($value);
1689 break;
1690 case 'blur':
1691 if (!$this->NO_IM_EFFECTS) {
1692 if ($this->V5_EFFECTS) {
1693 $commands .= $this->v5_blur($value);
1694 } else {
1695 $commands .= ' -blur ' . \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($value, 1, 99);
1696 }
1697 }
1698 break;
1699 case 'sharpen':
1700 if (!$this->NO_IM_EFFECTS) {
1701 if ($this->V5_EFFECTS) {
1702 $commands .= $this->v5_sharpen($value);
1703 } else {
1704 $commands .= ' -sharpen ' . \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($value, 1, 99);
1705 }
1706 }
1707 break;
1708 case 'rotate':
1709 $commands .= ' -rotate ' . \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($value, 0, 360);
1710 break;
1711 case 'solarize':
1712 $commands .= ' -solarize ' . \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($value, 0, 99);
1713 break;
1714 case 'swirl':
1715 $commands .= ' -swirl ' . \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($value, 0, 1000);
1716 break;
1717 case 'wave':
1718 $params = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $value);
1719 $commands .= ' -wave ' . \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($params[0], 0, 99) . 'x' . \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($params[1], 0, 99);
1720 break;
1721 case 'charcoal':
1722 $commands .= ' -charcoal ' . \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($value, 0, 100);
1723 break;
1724 case 'gray':
1725 $commands .= ' -colorspace GRAY';
1726 break;
1727 case 'edge':
1728 $commands .= ' -edge ' . \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($value, 0, 99);
1729 break;
1730 case 'emboss':
1731 $commands .= ' -emboss';
1732 break;
1733 case 'flip':
1734 $commands .= ' -flip';
1735 break;
1736 case 'flop':
1737 $commands .= ' -flop';
1738 break;
1739 case 'colors':
1740 $commands .= ' -colors ' . \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($value, 2, 255);
1741 break;
1742 case 'shear':
1743 $commands .= ' -shear ' . \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($value, -90, 90);
1744 break;
1745 case 'invert':
1746 $commands .= ' -negate';
1747 break;
1748 }
1749 }
1750 return $commands;
1751 }
1752
1753 /**
1754 * Implements the "ADJUST" GIFBUILDER object
1755 *
1756 * @param pointer $im GDlib image pointer
1757 * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1758 * @return void
1759 * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make(), autoLevels(), outputLevels(), inputLevels()
1760 * @todo Define visibility
1761 */
1762 public function adjust(&$im, $conf) {
1763 $setup = $conf['value'];
1764 if (!trim($setup)) {
1765 return '';
1766 }
1767 $effects = explode('|', $setup);
1768 foreach ($effects as $val) {
1769 $pairs = explode('=', $val, 2);
1770 $value = trim($pairs[1]);
1771 $effect = strtolower(trim($pairs[0]));
1772 switch ($effect) {
1773 case 'inputlevels':
1774 // low,high
1775 $params = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $value);
1776 $this->inputLevels($im, $params[0], $params[1]);
1777 break;
1778 case 'outputlevels':
1779 $params = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $value);
1780 $this->outputLevels($im, $params[0], $params[1]);
1781 break;
1782 case 'autolevels':
1783 $this->autoLevels($im);
1784 break;
1785 }
1786 }
1787 }
1788
1789 /**
1790 * Implements the "CROP" GIFBUILDER object
1791 *
1792 * @param pointer $im GDlib image pointer
1793 * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1794 * @return void
1795 * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
1796 * @todo Define visibility
1797 */
1798 public function crop(&$im, $conf) {
1799 // Clears workArea to total image
1800 $this->setWorkArea('');
1801 $cords = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $conf['crop'] . ',,,');
1802 $conf['offset'] = $cords[0] . ',' . $cords[1];
1803 $cords = $this->objPosition($conf, $this->workArea, array($cords[2], $cords[3]));
1804 $newIm = imagecreatetruecolor($cords[2], $cords[3]);
1805 $cols = $this->convertColor($conf['backColor'] ? $conf['backColor'] : $this->setup['backColor']);
1806 $Bcolor = ImageColorAllocate($newIm, $cols[0], $cols[1], $cols[2]);
1807 ImageFilledRectangle($newIm, 0, 0, $cords[2], $cords[3], $Bcolor);
1808 $newConf = array();
1809 $workArea = array(0, 0, $cords[2], $cords[3]);
1810 if ($cords[0] < 0) {
1811 $workArea[0] = abs($cords[0]);
1812 } else {
1813 $newConf['offset'] = -$cords[0];
1814 }
1815 if ($cords[1] < 0) {
1816 $workArea[1] = abs($cords[1]);
1817 } else {
1818 $newConf['offset'] .= ',' . -$cords[1];
1819 }
1820 $this->copyGifOntoGif($newIm, $im, $newConf, $workArea);
1821 $im = $newIm;
1822 $this->w = imagesx($im);
1823 $this->h = imagesy($im);
1824 // Clears workArea to total image
1825 $this->setWorkArea('');
1826 }
1827
1828 /**
1829 * Implements the "SCALE" GIFBUILDER object
1830 *
1831 * @param pointer $im GDlib image pointer
1832 * @param array $conf TypoScript array with configuration for the GIFBUILDER object.
1833 * @return void
1834 * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
1835 * @todo Define visibility
1836 */
1837 public function scale(&$im, $conf) {
1838 if ($conf['width'] || $conf['height'] || $conf['params']) {
1839 $tmpStr = $this->randomName();
1840 $theFile = $tmpStr . '.' . $this->gifExtension;
1841 $this->ImageWrite($im, $theFile);
1842 $theNewFile = $this->imageMagickConvert($theFile, $this->gifExtension, $conf['width'], $conf['height'], $conf['params']);
1843 $tmpImg = $this->imageCreateFromFile($theNewFile[3]);
1844 if ($tmpImg) {
1845 ImageDestroy($im);
1846 $im = $tmpImg;
1847 $this->w = imagesx($im);
1848 $this->h = imagesy($im);
1849 // Clears workArea to total image
1850 $this->setWorkArea('');
1851 }
1852 if (!$this->dontUnlinkTempFiles) {
1853 unlink($theFile);
1854 if ($theNewFile[3] && $theNewFile[3] != $theFile) {
1855 unlink($theNewFile[3]);
1856 }
1857 }
1858 }
1859 }
1860
1861 /**
1862 * Implements the "WORKAREA" GIFBUILDER object when setting it
1863 * Setting internal working area boundaries (->workArea)
1864 *
1865 * @param string $workArea Working area dimensions, comma separated
1866 * @return void
1867 * @access private
1868 * @see \TYPO3\CMS\Frontend\Imaging\GifBuilder::make()
1869 * @todo Define visibility
1870 */
1871 public function setWorkArea($workArea) {
1872 $this->workArea = \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $workArea);
1873 $this->workArea = $this->applyOffset($this->workArea, $this->OFFSET);
1874 if (!$this->workArea[2]) {
1875 $this->workArea[2] = $this->w;
1876 }
1877 if (!$this->workArea[3]) {
1878 $this->workArea[3] = $this->h;
1879 }
1880 }
1881
1882 /*************************
1883 *
1884 * Adjustment functions
1885 *
1886 ************************/
1887 /**
1888 * Apply auto-levels to input image pointer
1889 *
1890 * @param integer $im GDlib Image Pointer
1891 * @return void
1892 * @todo Define visibility
1893 */
1894 public function autolevels(&$im) {
1895 $totalCols = ImageColorsTotal($im);
1896 $min = 255;
1897 $max = 0;
1898 for ($c = 0; $c < $totalCols; $c++) {
1899 $cols = ImageColorsForIndex($im, $c);
1900 $grayArr[] = round(($cols['red'] + $cols['green'] + $cols['blue']) / 3);
1901 }
1902 $min = min($grayArr);
1903 $max = max($grayArr);
1904 $delta = $max - $min;
1905 if ($delta) {
1906 for ($c = 0; $c < $totalCols; $c++) {
1907 $cols = ImageColorsForIndex($im, $c);
1908 $cols['red'] = floor(($cols['red'] - $min) / $delta * 255);
1909 $cols['green'] = floor(($cols['green'] - $min) / $delta * 255);
1910 $cols['blue'] = floor(($cols['blue'] - $min) / $delta * 255);
1911 ImageColorSet($im, $c, $cols['red'], $cols['green'], $cols['blue']);
1912 }
1913 }
1914 }
1915
1916 /**
1917 * Apply output levels to input image pointer (decreasing contrast)
1918 *
1919 * @param integer $im GDlib Image Pointer
1920 * @param integer $low The "low" value (close to 0)
1921 * @param integer $high The "high" value (close to 255)
1922 * @param boolean $swap If swap, then low and high are swapped. (Useful for negated masks...)
1923 * @return void
1924 * @todo Define visibility
1925 */
1926 public function outputLevels(&$im, $low, $high, $swap = '') {
1927 if ($low < $high) {
1928 $low = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($low, 0, 255);
1929 $high = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($high, 0, 255);
1930 if ($swap) {
1931 $temp = $low;
1932 $low = 255 - $high;
1933 $high = 255 - $temp;
1934 }
1935 $delta = $high - $low;
1936 $totalCols = ImageColorsTotal($im);
1937 for ($c = 0; $c < $totalCols; $c++) {
1938 $cols = ImageColorsForIndex($im, $c);
1939 $cols['red'] = $low + floor($cols['red'] / 255 * $delta);
1940 $cols['green'] = $low + floor($cols['green'] / 255 * $delta);
1941 $cols['blue'] = $low + floor($cols['blue'] / 255 * $delta);
1942 ImageColorSet($im, $c, $cols['red'], $cols['green'], $cols['blue']);
1943 }
1944 }
1945 }
1946
1947 /**
1948 * Apply input levels to input image pointer (increasing contrast)
1949 *
1950 * @param integer $im GDlib Image Pointer
1951 * @param integer $low The "low" value (close to 0)
1952 * @param integer $high The "high" value (close to 255)
1953 * @param boolean $swap If swap, then low and high are swapped. (Useful for negated masks...)
1954 * @return void
1955 * @todo Define visibility
1956 */
1957 public function inputLevels(&$im, $low, $high, $swap = '') {
1958 if ($low < $high) {
1959 $low = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($low, 0, 255);
1960 $high = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($high, 0, 255);
1961 if ($swap) {
1962 $temp = $low;
1963 $low = 255 - $high;
1964 $high = 255 - $temp;
1965 }
1966 $delta = $high - $low;
1967 $totalCols = ImageColorsTotal($im);
1968 for ($c = 0; $c < $totalCols; $c++) {
1969 $cols = ImageColorsForIndex($im, $c);
1970 $cols['red'] = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange(($cols['red'] - $low) / $delta * 255, 0, 255);
1971 $cols['green'] = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange(($cols['green'] - $low) / $delta * 255, 0, 255);
1972 $cols['blue'] = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange(($cols['blue'] - $low) / $delta * 255, 0, 255);
1973 ImageColorSet($im, $c, $cols['red'], $cols['green'], $cols['blue']);
1974 }
1975 }
1976 }
1977
1978 /**
1979 * Reduce colors in image using IM and create a palette based image if possible (<=256 colors)
1980 *
1981 * @param string $file Image file to reduce
1982 * @param integer $cols Number of colors to reduce the image to.
1983 * @return string Reduced file
1984 * @todo Define visibility
1985 */
1986 public function IMreduceColors($file, $cols) {
1987 $fI = \TYPO3\CMS\Core\Utility\GeneralUtility::split_fileref($file);
1988 $ext = strtolower($fI['fileext']);
1989 $result = $this->randomName() . '.' . $ext;
1990 if (($reduce = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($cols, 0, $ext == 'gif' ? 256 : $this->truecolorColors, 0)) > 0) {
1991 $params = ' -colors ' . $reduce;
1992 if ($reduce <= 256) {
1993 $params .= ' -type Palette';
1994 }
1995 if ($ext == 'png' && $reduce <= 256) {
1996 $prefix = 'png8:';
1997 }
1998 $this->imageMagickExec($file, $prefix . $result, $params);
1999 if ($result) {
2000 return $result;
2001 }
2002 }
2003 return '';
2004 }
2005
2006 /*********************************
2007 *
2008 * GIFBUILDER Helper functions
2009 *
2010 *********************************/
2011 /**
2012 * Checks if the $fontFile is already at an absolute path and if not, prepends the correct path.
2013 * Use PATH_site unless we are in the backend.
2014 * Call it by t3lib_stdGraphic::prependAbsolutePath()
2015 *
2016 * @param string $fontFile The font file
2017 * @return string The font file with absolute path.
2018 * @todo Define visibility
2019 */
2020 public function prependAbsolutePath($fontFile) {
2021 $absPath = defined('PATH_typo3') ? dirname(PATH_thisScript) . '/' : PATH_site;
2022 $fontFile = \TYPO3\CMS\Core\Utility\GeneralUtility::isAbsPath($fontFile) ? $fontFile : \TYPO3\CMS\Core\Utility\GeneralUtility::resolveBackPath($absPath . $fontFile);
2023 return $fontFile;
2024 }
2025
2026 /**
2027 * Returns the IM command for sharpening with ImageMagick 5 (when $this->V5_EFFECTS is set).
2028 * Uses $this->im5fx_sharpenSteps for translation of the factor to an actual command.
2029 *
2030 * @param integer $factor The sharpening factor, 0-100 (effectively in 10 steps)
2031 * @return string The sharpening command, eg. " -sharpen 3x4
2032 * @see makeText(), IMparams(), v5_blur()
2033 * @todo Define visibility
2034 */
2035 public function v5_sharpen($factor) {
2036 $factor = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange(ceil($factor / 10), 0, 10);
2037 $sharpenArr = explode(',', ',' . $this->im5fx_sharpenSteps);
2038 $sharpenF = trim($sharpenArr[$factor]);
2039 if ($sharpenF) {
2040 $cmd = ' -sharpen ' . $sharpenF;
2041 return $cmd;
2042 }
2043 }
2044
2045 /**
2046 * Returns the IM command for blurring with ImageMagick 5 (when $this->V5_EFFECTS is set).
2047 * Uses $this->im5fx_blurSteps for translation of the factor to an actual command.
2048 *
2049 * @param integer $factor The blurring factor, 0-100 (effectively in 10 steps)
2050 * @return string The blurring command, eg. " -blur 3x4
2051 * @see makeText(), IMparams(), v5_sharpen()
2052 * @todo Define visibility
2053 */
2054 public function v5_blur($factor) {
2055 $factor = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange(ceil($factor / 10), 0, 10);
2056 $blurArr = explode(',', ',' . $this->im5fx_blurSteps);
2057 $blurF = trim($blurArr[$factor]);
2058 if ($blurF) {
2059 $cmd = ' -blur ' . $blurF;
2060 return $cmd;
2061 }
2062 }
2063
2064 /**
2065 * Returns a random filename prefixed with "temp_" and then 32 char md5 hash (without extension) from $this->tempPath.
2066 * Used by functions in this class to create truely temporary files for the on-the-fly processing. These files will most likely be deleted right away.
2067 *
2068 * @return string
2069 * @todo Define visibility
2070 */
2071 public function randomName() {
2072 $this->createTempSubDir('temp/');
2073 return $this->tempPath . 'temp/' . md5(uniqid(''));
2074 }
2075
2076 /**
2077 * Applies offset value to coordinated in $cords.
2078 * Basically the value of key 0/1 of $OFFSET is added to keys 0/1 of $cords
2079 *
2080 * @param array $cords Integer coordinates in key 0/1
2081 * @param array $OFFSET Offset values in key 0/1
2082 * @return array Modified $cords array
2083 * @todo Define visibility
2084 */
2085 public function applyOffset($cords, $OFFSET) {
2086 $cords[0] = intval($cords[0]) + intval($OFFSET[0]);
2087 $cords[1] = intval($cords[1]) + intval($OFFSET[1]);
2088 return $cords;
2089 }
2090
2091 /**
2092 * Converts a "HTML-color" TypoScript datatype to RGB-values.
2093 * Default is 0,0,0
2094 *
2095 * @param string $string "HTML-color" data type string, eg. 'red', '#ffeedd' or '255,0,255'. You can also add a modifying operator afterwards. There are two options: "255,0,255 : 20" - will add 20 to values, result is "255,20,255". Or "255,0,255 : *1.23" which will multiply all RGB values with 1.23
2096 * @return array RGB values in key 0/1/2 of the array
2097 * @todo Define visibility
2098 */
2099 public function convertColor($string) {
2100 $col = array();
2101 $cParts = explode(':', $string, 2);
2102 // Finding the RGB definitions of the color:
2103 $string = $cParts[0];
2104 if (strstr($string, '#')) {
2105 $string = preg_replace('/[^A-Fa-f0-9]*/', '', $string);
2106 $col[] = HexDec(substr($string, 0, 2));
2107 $col[] = HexDec(substr($string, 2, 2));
2108 $col[] = HexDec(substr($string, 4, 2));
2109 } elseif (strstr($string, ',')) {
2110 $string = preg_replace('/[^,0-9]*/', '', $string);
2111 $strArr = explode(',', $string);
2112 $col[] = intval($strArr[0]);
2113 $col[] = intval($strArr[1]);
2114 $col[] = intval($strArr[2]);
2115 } else {
2116 $string = strtolower(trim($string));
2117 if ($this->colMap[$string]) {
2118 $col = $this->colMap[$string];
2119 } else {
2120 $col = array(0, 0, 0);
2121 }
2122 }
2123 // ... and possibly recalculating the value
2124 if (trim($cParts[1])) {
2125 $cParts[1] = trim($cParts[1]);
2126 if (substr($cParts[1], 0, 1) == '*') {
2127 $val = doubleval(substr($cParts[1], 1));
2128 $col[0] = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($col[0] * $val, 0, 255);
2129 $col[1] = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($col[1] * $val, 0, 255);
2130 $col[2] = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($col[2] * $val, 0, 255);
2131 } else {
2132 $val = intval($cParts[1]);
2133 $col[0] = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($col[0] + $val, 0, 255);
2134 $col[1] = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($col[1] + $val, 0, 255);
2135 $col[2] = \TYPO3\CMS\Core\Utility\MathUtility::forceIntegerInRange($col[2] + $val, 0, 255);
2136 }
2137 }
2138 return $col;
2139 }
2140
2141 /**
2142 * Recode string
2143 * Used with text strings for fonts when languages has other character sets.
2144 *
2145 * @param string The text to recode
2146 * @return string The recoded string. Should be UTF-8 output. MAY contain entities (eg. &#123; or &#quot; which should render as real chars).
2147 * @todo Define visibility
2148 */
2149 public function recodeString($string) {
2150 // Recode string to UTF-8 from $this->nativeCharset:
2151 if ($this->nativeCharset && $this->nativeCharset != 'utf-8') {
2152 // Convert to UTF-8
2153 $string = $this->csConvObj->utf8_encode($string, $this->nativeCharset);
2154 }
2155 return $string;
2156 }
2157
2158 /**
2159 * Split a string into an array of individual characters
2160 * The function will look at $this->nativeCharset and if that is set, the input string is expected to be UTF-8 encoded, possibly with entities in it. Otherwise the string is supposed to be a single-byte charset which is just splitted by a for-loop.
2161 *
2162 * @param string $theText The text string to split
2163 * @param boolean $returnUnicodeNumber Return Unicode numbers instead of chars.
2164 * @return array Numerical array with a char as each value.
2165 * @todo Define visibility
2166 */
2167 public function singleChars($theText, $returnUnicodeNumber = FALSE) {
2168 if ($this->nativeCharset) {
2169 // Get an array of separated UTF-8 chars
2170 return $this->csConvObj->utf8_to_numberarray($theText, 1, $returnUnicodeNumber ? 0 : 1);
2171 } else {
2172 $output = array();
2173 $c = strlen($theText);
2174 for ($a = 0; $a < $c; $a++) {
2175 $output[] = substr($theText, $a, 1);
2176 }
2177 return $output;
2178 }
2179 }
2180
2181 /**
2182 * Create an array with object position/boundaries based on input TypoScript configuration (such as the "align" property is used), the work area definition and $BB array
2183 *
2184 * @param array $conf TypoScript configuration for a GIFBUILDER object
2185 * @param array makeBox Workarea definition
2186 * @param array $BB BB (Bounding box) array. Not just used for TEXT objects but also for others
2187 * @return array [0]=x, [1]=y, [2]=w, [3]=h
2188 * @access private
2189 * @see copyGifOntoGif(), makeBox(), crop()
2190 * @todo Define visibility
2191 */
2192 public function objPosition($conf, $workArea, $BB) {
2193 // offset, align, valign, workarea
2194 $result = array();
2195 $result[2] = $BB[0];
2196 $result[3] = $BB[1];
2197 $w = $workArea[2];
2198 $h = $workArea[3];
2199 $align = explode(',', $conf['align']);
2200 $align[0] = strtolower(substr(trim($align[0]), 0, 1));
2201 $align[1] = strtolower(substr(trim($align[1]), 0, 1));
2202 switch ($align[0]) {
2203 case 'r':
2204 $result[0] = $w - $result[2];
2205 break;
2206 case 'c':
2207 $result[0] = round(($w - $result[2]) / 2);
2208 break;
2209 default:
2210 $result[0] = 0;
2211 break;
2212 }
2213 switch ($align[1]) {
2214 case 'b':
2215 // y pos
2216 $result[1] = $h - $result[3];
2217 break;
2218 case 'c':
2219 $result[1] = round(($h - $result[3]) / 2);
2220 break;
2221 default:
2222 $result[1] = 0;
2223 break;
2224 }
2225 $result = $this->applyOffset($result, \TYPO3\CMS\Core\Utility\GeneralUtility::intExplode(',', $conf['offset']));
2226 $result = $this->applyOffset($result, $workArea);
2227 return $result;
2228 }
2229
2230 /***********************************
2231 *
2232 * Scaling, Dimensions of images
2233 *
2234 ***********************************/
2235 /**
2236 * Converts $imagefile to another file in temp-dir of type $newExt (extension).
2237 *
2238 * @param string $imagefile The image filepath
2239 * @param string $newExt New extension, eg. "gif", "png", "jpg", "tif". If $newExt is NOT set, the new imagefile will be of the original format. If newExt = 'WEB' then one of the web-formats is applied.
2240 * @param string $w Width. $w / $h is optional. If only one is given the image is scaled proportionally. If an 'm' exists in the $w or $h and if both are present the $w and $h is regarded as the Maximum w/h and the proportions will be kept
2241 * @param string $h Height. See $w
2242 * @param string $params Additional ImageMagick parameters.
2243 * @param string $frame Refers to which frame-number to select in the image. '' or 0 will select the first frame, 1 will select the next and so on...
2244 * @param array $options An array with options passed to getImageScale (see this function).
2245 * @param boolean $mustCreate If set, then another image than the input imagefile MUST be returned. Otherwise you can risk that the input image is good enough regarding messures etc and is of course not rendered to a new, temporary file in typo3temp/. But this option will force it to.
2246 * @return array [0]/[1] is w/h, [2] is file extension and [3] is the filename.
2247 * @see getImageScale(), typo3/show_item.php, fileList_ext::renderImage(), tslib_cObj::getImgResource(), SC_tslib_showpic::show(), maskImageOntoImage(), copyImageOntoImage(), scale()
2248 * @todo Define visibility
2249 */
2250 public function imageMagickConvert($imagefile, $newExt = '', $w = '', $h = '', $params = '', $frame = '', $options = array(), $mustCreate = FALSE) {
2251 if ($this->NO_IMAGE_MAGICK) {
2252 // Returning file info right away
2253 return $this->getImageDimensions($imagefile);
2254 }
2255 if ($info = $this->getImageDimensions($imagefile)) {
2256 $newExt = strtolower(trim($newExt));
2257 // If no extension is given the original extension is used
2258 if (!$newExt) {
2259 $newExt = $info[2];
2260 }
2261 if ($newExt == 'web') {
2262 if (\TYPO3\CMS\Core\Utility\GeneralUtility::inList($this->webImageExt, $info[2])) {
2263 $newExt = $info[2];
2264 } else {
2265 $newExt = $this->gif_or_jpg($info[2], $info[0], $info[1]);
2266 if (!$params) {
2267 $params = $this->cmds[$newExt];
2268 }
2269 }
2270 }
2271 if (\TYPO3\CMS\Core\Utility\GeneralUtility::inList($this->imageFileExt, $newExt)) {
2272 if (strstr($w . $h, 'm')) {
2273 $max = 1;
2274 } else {
2275 $max = 0;
2276 }
2277 $data = $this->getImageScale($info, $w, $h, $options);
2278 $w = $data['origW'];
2279 $h = $data['origH'];
2280 // If no conversion should be performed
2281 // this flag is TRUE if the width / height does NOT dictate
2282 // the image to be scaled!! (that is if no width / height is
2283 // given or if the destination w/h matches the original image
2284 // dimensions or if the option to not scale the image is set)
2285 $noScale = !$w && !$h || $data[0] == $info[0] && $data[1] == $info[1] || $options['noScale'];
2286 if ($noScale && !$data['crs'] && !$params && !$frame && $newExt == $info[2] && !$mustCreate) {
2287 // Set the new width and height before returning,
2288 // if the noScale option is set
2289 if (!empty($options['noScale'])) {
2290 $info[0] = $data[0];
2291 $info[1] = $data[1];
2292 }
2293 $info[3] = $imagefile;
2294 return $info;
2295 }
2296 $info[0] = $data[0];
2297 $info[1] = $data[1];
2298 $frame = $this->noFramePrepended ? '' : intval($frame);
2299 if (!$params) {
2300 $params = $this->cmds[$newExt];
2301 }
2302 // Cropscaling:
2303 if ($data['crs']) {
2304 if (!$data['origW']) {
2305 $data['origW'] = $data[0];
2306 }
2307 if (!$data['origH']) {
2308 $data['origH'] = $data[1];
2309 }
2310 $offsetX = intval(($data[0] - $data['origW']) * ($data['cropH'] + 100) / 200);
2311 $offsetY = intval(($data[1] - $data['origH']) * ($data['cropV'] + 100) / 200);
2312 $params .= ' -crop ' . $data['origW'] . 'x' . $data['origH'] . '+' . $offsetX . '+' . $offsetY . ' ';
2313 }
2314 $command = $this->scalecmd . ' ' . $info[0] . 'x' . $info[1] . '! ' . $params . ' ';
2315 $cropscale = $data['crs'] ? 'crs-V' . $data['cropV'] . 'H' . $data['cropH'] : '';
2316 if ($this->alternativeOutputKey) {
2317 $theOutputName = \TYPO3\CMS\Core\Utility\GeneralUtility::shortMD5($command . $cropscale . basename($imagefile) . $this->alternativeOutputKey . '[' . $frame . ']');
2318 } else {
2319 $theOutputName = \TYPO3\CMS\Core\Utility\GeneralUtility::shortMD5($command . $cropscale . $imagefile . filemtime($imagefile) . '[' . $frame . ']');
2320 }
2321 if ($this->imageMagickConvert_forceFileNameBody) {
2322 $theOutputName = $this->imageMagickConvert_forceFileNameBody;
2323 $this->imageMagickConvert_forceFileNameBody = '';
2324 }
2325 // Making the temporary filename:
2326 $this->createTempSubDir('pics/');
2327 $output = $this->absPrefix . $this->tempPath . 'pics/' . $this->filenamePrefix . $theOutputName . '.' . $newExt;
2328 // Register temporary filename:
2329 $GLOBALS['TEMP_IMAGES_ON_PAGE'][] = $output;
2330 if ($this->dontCheckForExistingTempFile || !$this->file_exists_typo3temp_file($output, $imagefile)) {
2331 $this->imageMagickExec($imagefile, $output, $command, $frame);
2332 }
2333 if (file_exists($output)) {
2334 $info[3] = $output;
2335 $info[2] = $newExt;
2336 // params could realisticly change some imagedata!
2337 if ($params) {
2338 $info = $this->getImageDimensions($info[3]);
2339 }
2340 if ($info[2] == $this->gifExtension && !$this->dontCompress) {
2341 // Compress with IM (lzw) or GD (rle) (Workaround for the absence of lzw-compression in GD)
2342 \TYPO3\CMS\Core\Utility\GeneralUtility::gif_compress($info[3], '');
2343 }
2344 return $info;
2345 }
2346 }
2347 }
2348 }
2349
2350 /**
2351 * Gets the input image dimensions.
2352 *
2353 * @param string $imageFile The image filepath
2354 * @return array Returns an array where [0]/[1] is w/h, [2] is extension and [3] is the filename.
2355 * @see imageMagickConvert(), tslib_cObj::getImgResource()
2356 * @todo Define visibility
2357 */
2358 public function getImageDimensions($imageFile) {
2359 preg_match('/([^\\.]*)$/', $imageFile, $reg);
2360 if (file_exists($imageFile) && \TYPO3\CMS\Core\Utility\GeneralUtility::inList($this->imageFileExt, strtolower($reg[0]))) {
2361 if ($returnArr = $this->getCachedImageDimensions($imageFile)) {
2362 return $returnArr;
2363 } else {
2364 if ($temp = @getImageSize($imageFile)) {
2365 $returnArr = array($temp[0], $temp[1], strtolower($reg[0]), $imageFile);
2366 } else {
2367 $returnArr = $this->imageMagickIdentify($imageFile);
2368 }
2369 if ($returnArr) {
2370 $this->cacheImageDimensions($returnArr);
2371 return $returnArr;
2372 }
2373 }
2374 }
2375 return FALSE;
2376 }
2377
2378 /**
2379 * Cache the result of the getImageDimensions function into the database. Does not check if the
2380 * file exists!
2381 *
2382 * @param array $identifyResult Result of the getImageDimensions function
2383 * @return boolean TRUE if operation was successful
2384 * @todo Define visibility
2385 */
2386 public function cacheImageDimensions($identifyResult) {
2387 // Create md5 hash of filemtime and filesize
2388 $md5Hash = md5(filemtime($identifyResult[3]) . filesize($identifyResult[3]));
2389 $result = FALSE;
2390 if ($md5Hash) {
2391 $fieldArray = array(
2392 'md5hash' => $md5Hash,
2393 'md5filename' => md5($identifyResult[3]),
2394 'tstamp' => $GLOBALS['EXEC_TIME'],
2395 'filename' => $identifyResult[3],
2396 'imagewidth' => $identifyResult[0],
2397 'imageheight' => $identifyResult[1]
2398 );
2399 $GLOBALS['TYPO3_DB']->exec_INSERTquery('cache_imagesizes', $fieldArray);
2400 if (!($err = $GLOBALS['TYPO3_DB']->sql_error())) {
2401 $result = TRUE;
2402 }
2403 }
2404 return $result;
2405 }
2406
2407 /**
2408 * Fetch the cached imageDimensions from the MySQL database. Does not check if the image file exists!
2409 *
2410 * @param string $imageFile The image filepath
2411 * @return array Returns an array where [0]/[1] is w/h, [2] is extension and [3] is the filename.
2412 * @todo Define visibility
2413 */
2414 public function getCachedImageDimensions($imageFile) {
2415 // Create md5 hash of filemtime and filesize
2416 $md5Hash = md5(filemtime($imageFile) . filesize($imageFile));
2417 $cachedImageDimensions = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow('md5hash, md5filename, imagewidth, imageheight', 'cache_imagesizes', 'md5filename=' . $GLOBALS['TYPO3_DB']->fullQuoteStr(md5($imageFile), 'cache_imagesizes'));
2418 $result = FALSE;
2419 if (is_array($cachedImageDimensions)) {
2420 if ($cachedImageDimensions['md5hash'] != $md5Hash) {
2421 // File has changed, delete the row
2422 $GLOBALS['TYPO3_DB']->exec_DELETEquery('cache_imagesizes', 'md5filename=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($cachedImageDimensions['md5filename'], 'cache_imagesizes'));
2423 } else {
2424 preg_match('/([^\\.]*)$/', $imageFile, $imageExtension);
2425 $result = array(
2426 (int) $cachedImageDimensions['imagewidth'],
2427 (int) $cachedImageDimensions['imageheight'],
2428 strtolower($imageExtension[0]),
2429 $imageFile
2430 );
2431 }
2432 }
2433 return $result;
2434 }
2435
2436 /**
2437 * Get numbers for scaling the image based on input
2438 *
2439 * @param array $info Current image information: Width, Height etc.
2440 * @param integer $w "required" width
2441 * @param integer $h "required" height
2442 * @param array $options Options: Keys are like "maxW", "maxH", "minW", "minH
2443 * @return array
2444 * @access private
2445 * @see imageMagickConvert()
2446 * @todo Define visibility
2447 */
2448 public function getImageScale($info, $w, $h, $options) {
2449 if (strstr($w . $h, 'm')) {
2450 $max = 1;
2451 } else {
2452 $max = 0;
2453 }
2454 if (strstr($w . $h, 'c')) {
2455 $out['cropH'] = intval(substr(strstr($w, 'c'), 1));
2456 $out['cropV'] = intval(substr(strstr($h, 'c'), 1));
2457 $crs = TRUE;
2458 } else {
2459 $crs = FALSE;
2460 }
2461 $out['crs'] = $crs;
2462 $w = intval($w);
2463 $h = intval($h);
2464 // If there are max-values...
2465 if (!empty($options['maxW'])) {
2466 // If width is given...
2467 if ($w) {
2468 if ($w > $options['maxW']) {
2469 $w = $options['maxW'];
2470 // Height should follow
2471 $max = 1;
2472 }
2473 } else {
2474 if ($info[0] > $options['maxW']) {
2475 $w = $options['maxW'];
2476 // Height should follow
2477 $max = 1;
2478 }
2479 }
2480 }
2481 if (!empty($options['maxH'])) {
2482 // If height is given...
2483 if ($h) {
2484 if ($h > $options['maxH']) {
2485 $h = $options['maxH'];
2486 // Height should follow
2487 $max = 1;
2488 }
2489 } else {
2490 // Changed [0] to [1] 290801
2491 if ($info[1] > $options['maxH']) {
2492 $h = $options['maxH'];
2493 // Height should follow
2494 $max = 1;
2495 }
2496 }
2497 }
2498 $out['origW'] = $w;
2499 $out['origH'] = $h;
2500 $out['max'] = $max;
2501 if (!$this->mayScaleUp) {
2502 if ($w > $info[0]) {
2503 $w = $info[0];
2504 }
2505 if ($h > $info[1]) {
2506 $h = $info[1];
2507 }
2508 }
2509 // If scaling should be performed
2510 if ($w || $h) {
2511 if ($w && !$h) {
2512 $info[1] = ceil($info[1] * ($w / $info[0]));
2513 $info[0] = $w;
2514 }
2515 if (!$w && $h) {
2516 $info[0] = ceil($info[0] * ($h / $info[1]));
2517 $info[1] = $h;
2518 }
2519 if ($w && $h) {
2520 if ($max) {
2521 $ratio = $info[0] / $info[1];
2522 if ($h * $ratio > $w) {
2523 $h = round($w / $ratio);
2524 } else {
2525 $w = round($h * $ratio);
2526 }
2527 }
2528 if ($crs) {
2529 $ratio = $info[0] / $info[1];
2530 if ($h * $ratio < $w) {
2531 $h = round($w / $ratio);
2532 } else {
2533 $w = round($h * $ratio);
2534 }
2535 }
2536 $info[0] = $w;
2537 $info[1] = $h;
2538 }
2539 }
2540 $out[0] = $info[0];
2541 $out[1] = $info[1];
2542 // Set minimum-measures!
2543 if (isset($options['minW']) && $out[0] < $options['minW']) {
2544 if (($max || $crs) && $out[0]) {
2545 $out[1] = round($out[1] * $options['minW'] / $out[0]);
2546 }
2547 $out[0] = $options['minW'];
2548 }
2549 if (isset($options['minH']) && $out[1] < $options['minH']) {
2550 if (($max || $crs) && $out[1]) {
2551 $out[0] = round($out[0] * $options['minH'] / $out[1]);
2552 }
2553 $out[1] = $options['minH'];
2554 }
2555 return $out;
2556 }
2557
2558 /**
2559 * Used to check if a certain process of scaling an image is already being carried out (can be logged in the SQL database)
2560 *
2561 * @param string $output Output imagefile
2562 * @param string $orig Original basis file
2563 * @return boolean Returns TRUE if the file is already being made; thus "TRUE" means "Don't render the image again
2564 * @access private
2565 * @todo Define visibility
2566 */
2567 public function file_exists_typo3temp_file($output, $orig = '') {
2568 if ($this->enable_typo3temp_db_tracking) {
2569 // If file exists, then we return immediately
2570 if (file_exists($output)) {
2571 return 1;
2572 } else {
2573 // If not, we look up in the cache_typo3temp_log table to see if there is a image being rendered right now.
2574 $md5Hash = md5($output);
2575 $res = $GLOBALS['TYPO3_DB']->exec_SELECTquery('md5hash', 'cache_typo3temp_log', 'md5hash=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($md5Hash, 'cache_typo3temp_log') . ' AND tstamp>' . ($GLOBALS['EXEC_TIME'] - 30));
2576 // If there was a record, the image is being generated by another process (we assume)
2577 if ($row = $GLOBALS['TYPO3_DB']->sql_fetch_assoc($res)) {
2578 $GLOBALS['TYPO3_DB']->sql_free_result($res);
2579 if (is_object($GLOBALS['TSFE'])) {
2580 $GLOBALS['TSFE']->set_no_cache('Another process renders this file now: ' . $output);
2581 }
2582 // ...so we set no_cache, because we dont want this page (which will NOT display an image...!) to be cached! (Only a page with the correct image on...)
2583 if (is_object($GLOBALS['TT'])) {
2584 $GLOBALS['TT']->setTSlogMessage('typo3temp_log: Assume this file is being rendered now: ' . $output);
2585 }
2586 // Return 'success - 2'
2587 return 2;
2588 } else {
2589 // If the current time is more than 30 seconds since this record was written, we clear the record, write a new and render the image.
2590 $insertFields = array(
2591 'md5hash' => $md5Hash,
2592 'tstamp' => $GLOBALS['EXEC_TIME'],
2593 'filename' => $output,
2594 'orig_filename' => $orig
2595 );
2596 $GLOBALS['TYPO3_DB']->exec_DELETEquery('cache_typo3temp_log', 'md5hash=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($md5Hash, 'cache_typo3temp_log'));
2597 $GLOBALS['TYPO3_DB']->exec_INSERTquery('cache_typo3temp_log', $insertFields);
2598 if (is_object($GLOBALS['TT'])) {
2599 $GLOBALS['TT']->setTSlogMessage('typo3temp_log: The row did not exist, so a new is written and file is being processed: ' . $output);
2600 }
2601 return 0;
2602 }
2603 }
2604 } else {
2605 return file_exists($output);
2606 }
2607 }
2608
2609 /***********************************
2610 *
2611 * ImageMagick API functions
2612 *
2613 ***********************************/
2614 /**
2615 * Returns an array where [0]/[1] is w/h, [2] is extension and [3] is the filename.
2616 * Using ImageMagick
2617 *
2618 * @param string $imagefile The relative (to PATH_site) image filepath
2619 * @return array
2620 * @todo Define visibility
2621 */
2622 public function imageMagickIdentify($imagefile) {
2623 if (!$this->NO_IMAGE_MAGICK) {
2624 $frame = $this->noFramePrepended ? '' : '[0]';
2625 $cmd = \TYPO3\CMS\Core\Utility\GeneralUtility::imageMagickCommand('identify', $this->wrapFileName($imagefile) . $frame);
2626 $returnVal = array();
2627 \TYPO3\CMS\Core\Utility\CommandUtility::exec($cmd, $returnVal);
2628 $splitstring = $returnVal[0];
2629 $this->IM_commands[] = array('identify', $cmd, $returnVal[0]);
2630 if ($splitstring) {
2631 preg_match('/([^\\.]*)$/', $imagefile, $reg);
2632 $splitinfo = explode(' ', $splitstring);
2633 foreach ($splitinfo as $key => $val) {
2634 $temp = '';
2635 if ($val) {
2636 $temp = explode('x', $val);
2637 }
2638 if (intval($temp[0]) && intval($temp[1])) {
2639 $dim = $temp;
2640 break;
2641 }
2642 }
2643 if ($dim[0] && $dim[1]) {
2644 return array($dim[0], $dim[1], strtolower($reg[0]), $imagefile);
2645 }
2646 }
2647 }
2648 }
2649
2650 /**
2651 * Executes a ImageMagick "convert" on two filenames, $input and $output using $params before them.
2652 * Can be used for many things, mostly scaling and effects.
2653 *
2654 * @param string $input The relative (to PATH_site) image filepath, input file (read from)
2655 * @param string $output The relative (to PATH_site) image filepath, output filename (written to)
2656 * @param string $params ImageMagick parameters
2657 * @param integer $frame Optional, refers to which frame-number to select in the image. '' or 0
2658 * @return string The result of a call to PHP function "exec()
2659 * @todo Define visibility
2660 */
2661 public function imageMagickExec($input, $output, $params, $frame = 0) {
2662 if (!$this->NO_IMAGE_MAGICK) {
2663 // Unless noFramePrepended is set in the Install Tool, a frame number is added to
2664 // select a specific page of the image (by default this will be the first page)
2665 if (!$this->noFramePrepended) {
2666 $frame = '[' . intval($frame) . ']';
2667 } else {
2668 $frame = '';
2669 }
2670 $cmd = \TYPO3\CMS\Core\Utility\GeneralUtility::imageMagickCommand('convert', $params . ' ' . $this->wrapFileName($input) . $frame . ' ' . $this->wrapFileName($output));
2671 $this->IM_commands[] = array($output, $cmd);
2672 $ret = \TYPO3\CMS\Core\Utility\CommandUtility::exec($cmd);
2673 // Change the permissions of the file
2674 \TYPO3\CMS\Core\Utility\GeneralUtility::fixPermissions($output);
2675 return $ret;
2676 }
2677 }
2678
2679 /**
2680 * Executes a ImageMagick "combine" (or composite in newer times) on four filenames - $input, $overlay and $mask as input files and $output as the output filename (written to)
2681 * Can be used for many things, mostly scaling and effects.
2682 *
2683 * @param string $input The relative (to PATH_site) image filepath, bottom file
2684 * @param string $overlay The relative (to PATH_site) image filepath, overlay file (top)
2685 * @param string $mask The relative (to PATH_site) image filepath, the mask file (grayscale)
2686 * @param string $output The relative (to PATH_site) image filepath, output filename (written to)
2687 * @param boolean $handleNegation
2688 * @return void
2689 * @todo Define visibility
2690 */
2691 public function combineExec($input, $overlay, $mask, $output, $handleNegation = FALSE) {
2692 if (!$this->NO_IMAGE_MAGICK) {
2693 $params = '-colorspace GRAY +matte';
2694 if ($handleNegation) {
2695 if ($this->maskNegate) {
2696 $params .= ' ' . $this->maskNegate;
2697 }
2698 }
2699 $theMask = $this->randomName() . '.' . $this->gifExtension;