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