Passed
Push — patch_1-1-9 ( dbab4a...54f508 )
by Spuds
01:12 queued 28s
created

getImageScaleFactor()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 7
dl 0
loc 17
rs 10
c 1
b 0
f 0
cc 3
nc 3
nop 3
1
<?php
2
3
/**
4
 * This file deals with low-level graphics operations performed on images,
5
 * specially as needed for avatars (uploaded avatars), attachments, or
6
 * visual verification images.
7
 *
8
 * @name      ElkArte Forum
9
 * @copyright ElkArte Forum contributors
10
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause
11
 *
12
 * This file contains code covered by:
13
 * copyright:	2011 Simple Machines (http://www.simplemachines.org)
14
 * license:  	BSD, See included LICENSE.TXT for terms and conditions.
15
 *
16
 * @version 1.1.9
17
 *
18
 */
19
20
/**
21
 * Create a thumbnail of the given source.
22
 *
23
 * @uses resizeImageFile() function to achieve the resize.
24
 * @package Graphics
25
 *
26
 * @param string $source The name of the source image
27
 * @param int $max_width The maximum allowed width
28
 * @param int $max_height The maximum allowed height
29
 *
30
 * @return boolean whether the thumbnail creation was successful.
31
 */
32
function createThumbnail($source, $max_width, $max_height)
33
{
34
	$destName = $source . '_thumb.tmp';
35
	$max_width = max(16, $max_width);
36
	$max_height = max(16, $max_height);
37
38
	// Do the actual resize, thumbnails by default strip EXIF data to save space
39
	$format = setDefaultFormat($source);
40
	$success = resizeImageFile($source, $destName, $max_width, $max_height, $format, true);
41
42
	// Okay, we're done with the temporary stuff.
43
	$destName = substr($destName, 0, -4);
44
45
	if ($success && @rename($destName . '.tmp', $destName))
46
		return true;
47
	else
48
	{
49
		@unlink($destName . '.tmp');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for unlink(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

49
		/** @scrutinizer ignore-unhandled */ @unlink($destName . '.tmp');

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
50
		@touch($destName);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for touch(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

50
		/** @scrutinizer ignore-unhandled */ @touch($destName);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
51
		return false;
52
	}
53
}
54
55
/**
56
 * Used to re-encodes an image to a specified image format
57
 *
58
 * What it does:
59
 *
60
 * - creates a copy of the file at the same location as fileName.
61
 * - the file would have the format preferred_format if possible, otherwise the default format is jpeg.
62
 * - the function makes sure that all non-essential image contents are disposed.
63
 *
64
 * @package Graphics
65
 * @param string $fileName The path to the file
66
 * @param int $preferred_format The preferred format
67
 *   - 0 to automatically determine
68
 *   - 1 for gif
69
 *   - 2 for jpg
70
 *   - 3 for png
71
 *   - 6 for bmp
72
 *   - 15 for wbmp
73
 *   - 18 for webp
74
 *
75
 * @return boolean true on success, false on failure.
76
 */
77
function reencodeImage($fileName, $preferred_format = 0)
78
{
79
	if (!resizeImageFile($fileName, $fileName . '.tmp', null, null, $preferred_format, true))
80
	{
81
		if (file_exists($fileName . '.tmp'))
82
			unlink($fileName . '.tmp');
83
84
		return false;
85
	}
86
87
	if (!unlink($fileName))
88
		return false;
89
90
	return rename($fileName . '.tmp', $fileName);
91
}
92
93
/**
94
 * Searches through the file to see if there's potentially harmful non-binary content.
95
 *
96
 * What it does:
97
 *
98
 * - if extensiveCheck is true, searches for asp/php short tags as well.
99
 *
100
 * @package Graphics
101
 *
102
 * @param string $fileName The path to the file
103
 * @param bool   $extensiveCheck = false if it should perform extensive checks
104
 *
105
 * @return bool Whether the image appears to be safe
106
 * @throws Elk_Exception attach_timeout
107
 */
108
function checkImageContents($fileName, $extensiveCheck = false)
109
{
110
	$fp = fopen($fileName, 'rb');
111
	if (!$fp)
0 ignored issues
show
introduced by
$fp is of type resource, thus it always evaluated to false.
Loading history...
112
	{
113
		loadLanguage('Post');
114
		throw new Elk_Exception('attach_timeout');
115
	}
116
117
	$prev_chunk = '';
118
	while (!feof($fp))
119
	{
120
		$cur_chunk = fread($fp, 8192);
121
		$test_chunk = $prev_chunk . $cur_chunk;
122
123
		// Though not exhaustive lists, better safe than sorry.
124
		if (!empty($extensiveCheck))
125
		{
126
			// Paranoid check. Some like it that way.
127
			if (preg_match('~<\\?php|<script\W|(?-i)[CFZ]WS[\x01-\x0E]~i', $test_chunk) === 1)
128
			{
129
				fclose($fp);
130
				return false;
131
			}
132
		}
133
		else
134
		{
135
			// Check for potential php injection
136
			if (preg_match('~<\\?php|<script\s+language\s*=\s*(?:php|"php"|\'php\')\s*>~i', $test_chunk) === 1)
137
			{
138
				fclose($fp);
139
				return false;
140
			}
141
		}
142
143
		$prev_chunk = $cur_chunk;
144
	}
145
146
	fclose($fp);
147
148
	return true;
149
}
150
151
/**
152
 * Sets a global $gd2 variable needed by some functions to determine
153
 * whether the GD2 library is present.
154
 *
155
 * @package Graphics
156
 *
157
 * @return bool Whether or not GD is available.
158
 */
159
function checkGD()
160
{
161
	global $gd2;
162
163
	// Check to see if GD is installed and what version.
164
	if (($extensionFunctions = get_extension_funcs('gd')) === false)
165
		return false;
166
167
	// Also determine if GD2 is installed and store it in a global.
168
	$gd2 = in_array('imagecreatetruecolor', $extensionFunctions) && function_exists('imagecreatetruecolor');
169
170
	return true;
171
}
172
173
/**
174
 * Checks whether the Imagick class is present.
175
 *
176
 * @package Graphics
177
 *
178
 * @return bool Whether or not the Imagick extension is available.
179
 */
180
function checkImagick()
181
{
182
	return class_exists('Imagick', false);
183
}
184
185
/**
186
 * Check if the system supports webP
187
 *
188
 * @return bool
189
 */
190
function hasWebpSupport()
191
{
192
	if (checkImagick())
193
	{
194
		$check = Imagick::queryformats();
195
		return in_array('WEBP', $check);
196
	}
197
198
	if (checkGD())
199
	{
200
		$check = gd_info();
201
		return !empty($check['WebP Support']);
202
	}
203
204
	return false;
205
}
206
207
/**
208
 * See if we have enough memory to thumbnail an image
209
 *
210
 * @package Graphics
211
 * @param int[] $sizes image size
212
 *
213
 * @return bool Whether or not the memory is available.
214
 */
215
function imageMemoryCheck($sizes)
216
{
217
	global $modSettings;
218
219
	// Just to be sure
220
	if (!is_array($sizes) || $sizes[0] === -1)
0 ignored issues
show
introduced by
The condition is_array($sizes) is always true.
Loading history...
221
	{
222
		return true;
223
	}
224
225
	// Doing the old 'set it and hope' way?
226
	if (empty($modSettings['attachment_thumb_memory']))
227
	{
228
		detectServer()->setMemoryLimit('128M');
229
		return true;
230
	}
231
232
	// Determine the memory requirements for this image, note: if you want to use an image formula
233
	// W x H x bits/8 x channels x Overhead factor
234
	// You will need to account for single bit images as GD expands them to an 8 bit and will greatly
235
	// overun the calculated value.
236
	// The 5 below is simply a shortcut of 8bpp, 3 channels, 1.66 overhead
237
	$needed_memory = ($sizes[0] * $sizes[1] * 5);
238
239
	// If we need more, lets try to get it
240
	return detectServer()->setMemoryLimit($needed_memory, true);
241
}
242
243
/**
244
 * Resize an image from a remote location or a local file.
245
 *
246
 * What it does:
247
 *
248
 * - Puts the resized image at the destination location.
249
 * - The file would have the format preferred_format if possible,
250
 * otherwise the default format is jpeg.
251
 *
252
 * @package Graphics
253
 *
254
 * @param string $source The name of the source image
255
 * @param string $destination The name of the destination image
256
 * @param int $max_width The maximum allowed width
257
 * @param int $max_height The maximum allowed height
258
 * @param int $preferred_format Used by Imagick/resizeImage
259
 * @param bool $strip Allow IM to remove exif data as GD always will
260
 * @param bool $force_resize Always resize the image (force scale up)
261
 *
262
 * @return boolean Whether the thumbnail creation was successful.
263
 */
264
function resizeImageFile($source, $destination, $max_width, $max_height, $preferred_format = 0, $strip = false, $force_resize = true)
265
{
266
	global $modSettings;
267
268
	// Nothing to do without GD or IM
269
	if (!checkGD() && !checkImagick())
270
		return false;
271
272
	if (!file_exists($source) && substr($source, 0, 7) !== 'http://' && substr($source, 0, 8) !== 'https://')
273
	{
274
		return false;
275
	}
276
277
	$default_formats = array(
278
		1 => 'gif',
279
		2 => 'jpeg',
280
		3 => 'png',
281
		6 => 'bmp',
282
		15 => 'wbmp',
283
		18 => 'webp'
284
	);
285
286
	require_once(SUBSDIR . '/Package.subs.php');
287
	require_once(SUBSDIR . '/Attachments.subs.php');
288
289
	// Get the image file, we have to work with something after all
290
	$fp_destination = fopen($destination, 'wb');
291
	if ($fp_destination && (substr($source, 0, 7) === 'http://' || substr($source, 0, 8) === 'https://'))
0 ignored issues
show
introduced by
$fp_destination is of type resource, thus it always evaluated to false.
Loading history...
292
	{
293
		$fileContents = fetch_web_data($source);
294
295
		fwrite($fp_destination, $fileContents);
296
		fclose($fp_destination);
297
298
		$sizes = elk_getimagesize($destination);
299
	}
300
	elseif ($fp_destination)
0 ignored issues
show
introduced by
$fp_destination is of type resource, thus it always evaluated to false.
Loading history...
301
	{
302
		$sizes = elk_getimagesize($source);
303
304
		$fp_source = fopen($source, 'rb');
305
		if ($fp_source !== false)
306
		{
307
			while (!feof($fp_source))
308
			{
309
				fwrite($fp_destination, fread($fp_source, 8192));
310
			}
311
			fclose($fp_source);
312
		}
313
		else
314
			$sizes = array(-1, -1, -1);
315
316
		fclose($fp_destination);
317
	}
318
	// We can't get to the file.
319
	else
320
		$sizes = array(-1, -1, -1);
321
322
	if ($sizes[0] === -1)
323
		return false;
324
325
	// See if we have -or- can get the needed memory for this operation
326
	if (checkGD() && !imageMemoryCheck($sizes))
327
		return false;
328
329
	// Not allowed to save webp or can't support webp input
330
	if ((empty($modSettings['attachment_webp_enable']) && $preferred_format == 18) || ($sizes[2] == 18 && !hasWebpSupport()))
331
		return false;
332
333
	// A known and supported format?
334
	if (checkImagick() && isset($default_formats[$sizes[2]]))
335
	{
336
		return resizeImage(null, $destination, null, null, $max_width, $max_height, $force_resize, $preferred_format, $strip);
337
	}
338
	elseif (checkGD() && isset($default_formats[$sizes[2]]) && function_exists('imagecreatefrom' . $default_formats[$sizes[2]]))
339
	{
340
		try
341
		{
342
			$imagecreatefrom = 'imagecreatefrom' . $default_formats[$sizes[2]];
343
			$src_img = $imagecreatefrom($destination);
344
			return resizeImage($src_img, $destination, imagesx($src_img), imagesy($src_img), $max_width === null ? imagesx($src_img) : $max_width, $max_height === null ? imagesy($src_img) : $max_height, $force_resize, $preferred_format, $strip);
0 ignored issues
show
introduced by
The condition $max_width === null is always false.
Loading history...
introduced by
The condition $max_height === null is always false.
Loading history...
345
		}
346
		catch (\Exception $e)
347
		{
348
			return false;
349
		}
350
	}
351
352
	return false;
353
}
354
355
/**
356
 * Resize an image proportionally to fit within the defined max_width and max_height limits
357
 *
358
 * What it does:
359
 *
360
 * - Will do nothing to the image if the file fits within the size limits
361
 * - If Image Magick is present it will use those function over any GD solutions
362
 * - If GD2 is present, it'll use it to achieve better quality (imagecopyresampled)
363
 * - Saves the new image to destination_filename, in the preferred_format
364
 * if possible, default is jpeg.
365
 *
366
 * @uses GD
367
 * @uses Imagick
368
 *
369
 * @package Graphics
370
 * @param resource|null $src_img null for Imagick images, resource form imagecreatefrom for GD
371
 * @param string $destName
372
 * @param int $src_width The width of the source image
373
 * @param int $src_height The height of the source image
374
 * @param int $max_width The maximum allowed width
375
 * @param int $max_height The maximum allowed height
376
 * @param bool $force_resize = false Whether to override defaults and resize it
377
 * @param int $preferred_format - The preferred format
378
 *   - 0 to use jpeg
379
 *   - 1 for gif
380
 *   - 2 to force jpeg
381
 *   - 3 for png
382
 *   - 6 for bmp
383
 *   - 15 for wbmp
384
 *   - 18 for webp
385
 * @param bool $strip Whether to have IM strip EXIF data as GD will
386
 *
387
 * @return bool Whether resize was successful.
388
 */
389
function resizeImage($src_img, $destName, $src_width, $src_height, $max_width, $max_height, $force_resize = false, $preferred_format = 0, $strip = false)
390
{
391
	global $gd2, $modSettings;
392
393
	if (checkImagick())
394
	{
395
		// These are the file formats we know about
396
		$default_formats = array(
397
			1 => 'gif',
398
			2 => 'jpeg',
399
			3 => 'png',
400
			6 => 'bmp',
401
			15 => 'wbmp',
402
			18 => 'webp'
403
		);
404
405
		// Just to be sure, if the admin does not want webp
406
		if (empty($modSettings['attachment_webp_enable']))
407
			unset($default_formats[18]);
408
409
		$preferred_format = empty($preferred_format) || !isset($default_formats[$preferred_format]) ? 2 : $preferred_format;
410
411
		// Since Imagick can throw exceptions, lets catch them
412
		try
413
		{
414
			// Get a new instance of Imagick for use
415
			$imagick = new Imagick($destName);
416
			$imagick->setFirstIterator();
417
418
			// Set the input and output image size
419
			$src_width = empty($src_width) ? $imagick->getImageWidth() : $src_width;
420
			$src_height = empty($src_height) ? $imagick->getImageHeight() : $src_height;
421
422
			// The behavior of bestfit changed in Imagick 3.0.0 and it will now scale up, we prevent that
423
			$dest_width = empty($max_width) ? $src_width : ($force_resize ? $max_width : min($max_width, $src_width));
424
			$dest_height = empty($max_height) ? $src_height : ($force_resize ? $max_height :  min($max_height, $src_height));
425
426
			// Set jpeg image quality to 80
427
			if ($default_formats[$preferred_format] === 'jpeg')
428
			{
429
				$imagick->borderImage('white', 0, 0);
430
				$imagick->setImageCompression(Imagick::COMPRESSION_JPEG);
431
				$imagick->setImageCompressionQuality(80);
432
			}
433
434
			// With PNG Save a few bytes the only way, realistically, we can
435
			if ($default_formats[$preferred_format] === 'png')
436
			{
437
				$imagick->setOption('png:compression-level', '9');
438
				$imagick->setOption('png:exclude-chunk', 'all');
439
			}
440
441
			// Webp
442
			if ($default_formats[$preferred_format] === 'webp')
443
			{
444
				$imagick->setImageCompressionQuality(80);
445
			}
446
447
			// Create a new image in our preferred format and resize it if needed
448
			$imagick->setImageFormat($default_formats[$preferred_format]);
449
			if (substr($destName, -6) === '_thumb')
450
				$imagick->thumbnailImage($dest_width, $dest_height, true);
451
			else
452
				$imagick->resizeImage($dest_width, $dest_height, imagick::FILTER_LANCZOS, 1, true);
453
454
			// Remove EXIF / ICC data?
455
			if ($strip)
456
			{
457
				$imagick->stripImage();
458
			}
459
460
			// Save the new image in the destination location
461
			if ($preferred_format === IMAGETYPE_GIF && $imagick->getNumberImages() !== 0)
462
				$success = $imagick->writeImages($destName, true);
463
			else
464
				$success = $imagick->writeImage($destName);
465
466
			// Free resources associated with the Imagick object
467
			$imagick->clear();
468
		}
469
		catch (Exception $e)
470
		{
471
			$success = false;
472
		}
473
474
		return !empty($success);
475
	}
476
	elseif (checkGD())
477
	{
478
		$success = false;
479
480
		// Determine whether to resize to max width or to max height (depending on the limits.)
481
		if (!empty($max_width) || !empty($max_height))
482
		{
483
			if (!empty($max_width) && (empty($max_height) || $src_height * $max_width / $src_width <= $max_height))
484
			{
485
				$dst_width = $max_width;
486
				$dst_height = floor($src_height * $max_width / $src_width);
487
			}
488
			elseif (!empty($max_height))
489
			{
490
				$dst_width = floor($src_width * $max_height / $src_height);
491
				$dst_height = $max_height;
492
			}
493
494
			// Don't bother resizing if it's already smaller...
495
			if (!empty($dst_width) && !empty($dst_height) && ($dst_width < $src_width || $dst_height < $src_height || $force_resize))
496
			{
497
				// (make a true color image, because it just looks better for resizing.)
498
				if ($gd2)
499
				{
500
					$dst_img = imagecreatetruecolor($dst_width, $dst_height);
501
502
					// Make a true color image, because it just looks better for resizing.
503
					imagesavealpha($dst_img, true);
504
					$color = imagecolorallocatealpha($dst_img, 255, 255, 255, 127);
505
					imagefill($dst_img, 0, 0, $color);
506
507
					// Resize it!
508
					imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $dst_width, $dst_height, $src_width, $src_height);
509
				}
510
				else
511
				{
512
					$dst_img = imagecreate($dst_width, $dst_height);
513
					imagecopyresamplebicubic($dst_img, $src_img, 0, 0, 0, 0, $dst_width, $dst_height, $src_width, $src_height);
0 ignored issues
show
Bug introduced by
It seems like $dst_img can also be of type GdImage; however, parameter $dst_img of imagecopyresamplebicubic() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

513
					imagecopyresamplebicubic(/** @scrutinizer ignore-type */ $dst_img, $src_img, 0, 0, 0, 0, $dst_width, $dst_height, $src_width, $src_height);
Loading history...
514
				}
515
			}
516
			else
517
				$dst_img = $src_img;
518
		}
519
		else
520
			$dst_img = $src_img;
521
522
		// Save the image as ...
523
		if (!empty($preferred_format) && ($preferred_format == 3) && function_exists('imagepng'))
524
			$success = imagepng($dst_img, $destName, 9, PNG_ALL_FILTERS);
525
		elseif (!empty($preferred_format) && ($preferred_format == 1) && function_exists('imagegif'))
526
			$success = imagegif($dst_img, $destName);
527
		elseif (!empty($preferred_format) && $preferred_format == 18 && function_exists('imagewebp'))
528
			$success = imagewebp($dst_img, $destName, 80);
529
		elseif (function_exists('imagejpeg'))
530
			$success = imagejpeg($dst_img, $destName, 80);
531
532
		// Free the memory.
533
		imagedestroy($src_img);
534
		if ($dst_img != $src_img)
535
			imagedestroy($dst_img);
536
537
		return $success;
538
	}
539
	// Without Imagick or GD, no image resizing at all.
540
	else
541
		return false;
542
}
543
544
/*
545
 * Determines if an image has any alpha pixels
546
 *
547
 * @param string $fileName
548
 * @return bool
549
 */
550
function hasTransparency($fileName, $png = true)
551
{
552
	if (empty($fileName) || !file_exists($fileName))
553
		return false;
554
555
	$header = file_get_contents($fileName, false, null, 0, 26);
556
557
	// Does it even claim to have been saved with transparency
558
	if ($png && !ord($header[25]) & 4)
559
		return false;
560
561
	// Webp has its own
562
	if (!$png)
563
	{
564
		if (($header[15] === 'L' && !(ord($header[24]) & 16)) || ($header[15] === 'X' && !(ord($header[20]) & 16)))
565
		{
566
			return false;
567
		}
568
	}
569
570
	// Saved with transparency is only a start, now Pixel inspection
571
	if (checkImagick())
572
	{
573
		$transparency = false;
574
		try
575
		{
576
			$image = new Imagick($fileName);
577
			if ($image->getImageWidth() > 1024 || $image->getImageHeight() > 1024)
578
			{
579
				// Used to look for transparency, it is not intended to be a quality image.
580
				$scaleValue = getImageScaleFactor($image->getImageWidth(), $image->getImageHeight());
581
				$image->scaleImage($scaleValue[0], $scaleValue[1], true);
582
			}
583
584
			$pixel_iterator = $image->getPixelIterator();
585
586
			// Look at each one, or until we find the first alpha pixel
587
			foreach ($pixel_iterator as $pixels)
588
			{
589
				foreach ($pixels as $pixel)
590
				{
591
					$color = $pixel->getColor();
592
					if ($color['a'] < 1)
593
					{
594
						$transparency = true;
595
						break 2;
596
					}
597
				}
598
			}
599
		}
600
		catch (\ImagickException $e)
601
		{
602
			// We don't know what it is, so don't mess with it
603
			unset($image);
604
			return true;
605
		}
606
607
		unset($image);
608
609
		return $transparency;
610
	}
611
612
	if (checkGD())
613
	{
614
		// Go through the image pixel by pixel until we find a transparent pixel
615
		$transparency = false;
616
		list ($width, $height, $type) = elk_getimagesize($fileName);
617
618
		if ($type === 18 && hasWebpSupport())
619
			$image = imagecreatefromwebp($fileName);
620
		elseif ($type === 3)
621
			$image = imagecreatefrompng($fileName);
622
		else
623
			return false;
624
625
		// Large image, scale down to reduce processing time
626
		if ($width > 1024 || $height > 1024)
627
		{
628
			// Single pass scale down, not looking for quality here
629
			$scaleValue = getImageScaleFactor($width, $height);
630
			$image = imagescale($image, $scaleValue[0], $scaleValue[1], IMG_NEAREST_NEIGHBOUR);
631
			if (!$image)
632
			{
633
				return true;
634
			}
635
		}
636
637
		$x = imagesx($image);
638
		$y = imagesy($image);
639
		for ($i = 0; $i < $x; $i++)
640
		{
641
			for ($j = 0; $j < $y; $j++)
642
			{
643
				if (imagecolorat($image, $i, $j) & 0x7F000000)
644
				{
645
					$transparency = true;
646
					break 2;
647
				}
648
			}
649
		}
650
651
		unset($image);
652
653
		return $transparency;
654
	}
655
656
	// Don't know so assume true
657
	return true;
658
}
659
660
/**
661
 * Provides image scaling factors that maintain existing aspect ratio
662
 *
663
 * @param int $width current width
664
 * @param int $height current height
665
 * @param int $limit desired width/height limit
666
 * @return int[]
667
 */
668
function getImageScaleFactor($width, $height, $limit = 800)
669
{
670
	$thumb_w = $limit;
671
	$thumb_h = $limit;
672
673
	// Landscape
674
	if ($width > $height)
675
	{
676
		$thumb_h = max (1, $height * ($limit / $width));
677
	}
678
	// Portrait
679
	elseif ($width < $height)
680
	{
681
		$thumb_w = max(1, $width * ($limit / $height));
682
	}
683
684
	return array((int) $thumb_w, (int) $thumb_h);
685
}
686
687
/**
688
 * Sets the best output format for a given image's thumbnail
689
 *
690
 * - If webP is on, and supported, use that as it gives the smallest size
691
 * - If webP support is available, but not on, save as png if it has alpha channel pixels
692
 * - If the image has alpha, we preserve it as PNG
693
 * - Finally good ol' jpeg
694
 *
695
 * @return int 2, 3, or 18
696
 */
697
function setDefaultFormat($fileName = '')
698
{
699
	global $modSettings;
700
701
	// Webp is the best choice if server supports
702
	if (!empty($modSettings['attachment_webp_enable']) && hasWebpSupport())
703
	{
704
		return 18;
705
	}
706
707
	$mime = getMimeType($fileName);
708
709
	// They uploaded a webp image, but ACP does not allow saving webp images, then
710
	// if the server supports and its alpha save it as a png
711
	if ($mime === 'image/webp' && hasWebpSupport() && hasTransparency($fileName, false))
712
	{
713
		return 3;
714
	}
715
716
	// If you have alpha channels, best keep them with PNG
717
	if ($mime === 'image/png' && hasTransparency($fileName))
718
	{
719
		return 3;
720
	}
721
722
	// The default, JPG
723
	return 2;
724
}
725
726
/**
727
 * Best determination of the mime type.
728
 *
729
 * @return string
730
 */
731
function getMimeType($fileName)
732
{
733
	// Try Exif which reads the file headers, most accurate for images
734
	if (function_exists('exif_imagetype'))
735
	{
736
		return image_type_to_mime_type(exif_imagetype($fileName));
737
	}
738
739
	return get_finfo_mime($fileName);
0 ignored issues
show
Bug Best Practice introduced by
The expression return get_finfo_mime($fileName) also could return the type boolean which is incompatible with the documented return type string.
Loading history...
740
}
741
742
/**
743
 * Calls GD or ImageMagick functions to correct an images orientation
744
 * based on the EXIF orientation flag
745
 *
746
 * @param string $image_name
747
 */
748
function autoRotateImage($image_name)
749
{
750
	if (checkImagick())
751
	{
752
		autoRotateImageWithIM($image_name);
753
	}
754
	elseif (checkGD())
755
	{
756
		autoRotateImageWithGD($image_name);
757
	}
758
}
759
760
/**
761
 * Autorotate an image based on its EXIF Orientation tag.
762
 *
763
 * What it does:
764
 *
765
 * - GD only
766
 * - Checks exif data for orientation flag and rotates image so its proper
767
 * - Does not update orientation flag as GD removes EXIF data
768
 * - Only works with jpeg images, could add TIFF as well
769
 * - Writes the update image back to $image_name
770
 *
771
 * @package Graphics
772
 * @uses GD
773
 * @param string $image_name full location of the file
774
 */
775
function autoRotateImageWithGD($image_name)
776
{
777
	// Read the EXIF data
778
	$exif = function_exists('exif_read_data') ? @exif_read_data($image_name) : array();
779
780
	// We're only interested in the exif orientation
781
	$orientation = isset($exif['Orientation']) ? $exif['Orientation'] : 0;
782
783
	// For now we only process jpeg images, so check that we have one
784
	$sizes = elk_getimagesize($image_name);
785
786
	// Not a jpeg or not rotated, done!
787
	if ($sizes[2] !== 2 || $orientation === 0 || !imageMemoryCheck($sizes))
788
	{
789
		return false;
790
	}
791
792
	// Load the image object so we can begin the transformation(s)
793
	$source = imagecreatefromjpeg($image_name);
794
795
	// Time to spin and mirror as needed
796
	switch ($orientation)
797
	{
798
		// 0 & 1 Not set or Normal
799
		case 0:
800
		case 1:
801
			break;
802
		// 2 Mirror image, Normal orientation
803
		case 2:
804
			$source = flopImageGD($source, $sizes);
0 ignored issues
show
Bug introduced by
It seems like $source can also be of type GdImage; however, parameter $image of flopImageGD() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

804
			$source = flopImageGD(/** @scrutinizer ignore-type */ $source, $sizes);
Loading history...
805
			break;
806
		// 3 Normal image, rotated 180
807
		case 3:
808
			$source = rotateImageGD($source, 180);
0 ignored issues
show
Bug introduced by
It seems like $source can also be of type GdImage; however, parameter $image of rotateImageGD() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

808
			$source = rotateImageGD(/** @scrutinizer ignore-type */ $source, 180);
Loading history...
809
			break;
810
		// 4 Mirror image, rotated 180
811
		case 4:
812
			$source = flipImageGD($source, $sizes);
0 ignored issues
show
Bug introduced by
It seems like $source can also be of type GdImage; however, parameter $image of flipImageGD() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

812
			$source = flipImageGD(/** @scrutinizer ignore-type */ $source, $sizes);
Loading history...
813
			break;
814
		// 5 Mirror image, rotated 90 CCW
815
		case 5:
816
			$source = flopImageGD($source, $sizes);
817
			$source = rotateImageGD($source, 90);
818
			break;
819
		// 6 Normal image, rotated 90 CCW
820
		case 6:
821
			$source = rotateImageGD($source, -90);
822
			break;
823
		// 7 Mirror image, rotated 90 CW
824
		case 7:
825
			$source = flopImageGD($source, $sizes);
826
			$source = rotateImageGD($source, -90);
827
			break;
828
		// 8 Normal image, rotated 90 CW
829
		case 8:
830
			$source = rotateImageGD($source, 90);
831
			break;
832
		default:
833
			$orientation = 1;
834
			break;
835
	}
836
837
	// Save the updated image, free resources
838
	if ($orientation >= 2)
839
	{
840
		imagejpeg($source, $image_name);
841
	}
842
843
	imagedestroy($source);
844
845
	return true;
846
}
847
848
/**
849
 * Autorotate an image based on its EXIF Orientation tag.
850
 *
851
 * What it does:
852
 *
853
 * - ImageMagick only
854
 * - Checks exif data for orientation flag and rotates image so its proper
855
 * - Updates orientation flag if rotation was required
856
 * - Writes the update image back to $image_name
857
 *
858
 * @uses Imagick
859
 * @param string $image_name
860
 */
861
function autoRotateImageWithIM($image_name)
862
{
863
	// We only process jpeg images
864
	$sizes = elk_getimagesize($image_name);
865
866
	// Not a jpeg, no need to process
867
	if ($sizes[2] !== IMAGETYPE_JPEG)
868
	{
869
		return false;
870
	}
871
872
	try
873
	{
874
		// Get a new instance of Imagick for use
875
		$image = new Imagick($image_name);
876
877
		// This method should exist if Imagick has been compiled against ImageMagick version
878
		// 6.3.0 or higher which is forever ago, but we check anyway ;)
879
		if (!method_exists($image, 'getImageOrientation'))
880
		{
881
			return false;
882
		}
883
884
		$orientation = $image->getImageOrientation();
885
		switch ($orientation)
886
		{
887
			// 0 & 1 Not set or Normal
888
			case Imagick::ORIENTATION_UNDEFINED:
889
			case Imagick::ORIENTATION_TOPLEFT:
890
				break;
891
			// 2 Mirror image, Normal orientation
892
			case Imagick::ORIENTATION_TOPRIGHT:
893
				$image->flopImage();
894
				break;
895
			// 3 Normal image, rotated 180
896
			case Imagick::ORIENTATION_BOTTOMRIGHT:
897
				$image->rotateImage('#000', 180);
898
				break;
899
			// 4 Mirror image, rotated 180
900
			case Imagick::ORIENTATION_BOTTOMLEFT:
901
				$image->flipImage();
902
				break;
903
			// 5 Mirror image, rotated 90 CCW
904
			case Imagick::ORIENTATION_LEFTTOP:
905
				$image->rotateImage('#000', 90);
906
				$image->flopImage();
907
				break;
908
			// 6 Normal image, rotated 90 CCW
909
			case Imagick::ORIENTATION_RIGHTTOP:
910
				$image->rotateImage('#000', 90);
911
				break;
912
			// 7 Mirror image, rotated 90 CW
913
			case Imagick::ORIENTATION_RIGHTBOTTOM:
914
				$image->rotateImage('#000', -90);
915
				$image->flopImage();
916
				break;
917
			// 8 Normal image, rotated 90 CW
918
			case Imagick::ORIENTATION_LEFTBOTTOM:
919
				$image->rotateImage('#000', -90);
920
				break;
921
			default:
922
				$orientation = 1;
923
				break;
924
		}
925
926
		// Now that it's auto-rotated, make sure the EXIF data is correctly updated
927
		if ($orientation >= 2)
928
		{
929
			$image->setImageOrientation(Imagick::ORIENTATION_TOPLEFT);
930
931
			// Save the new image in the destination location
932
			$image->writeImage($image_name);
933
		}
934
935
		// Free resources associated with the Imagick object
936
		$image->clear();
937
	}
938
	catch (\ImagickException $e)
939
	{
940
		// pass through;
941
	}
942
943
	return true;
944
}
945
946
/**
947
 * Rotate an image by X degrees, GD function
948
 *
949
 * @param resource $image
950
 * @param int $degrees
951
 *
952
 * @package Graphics
953
 * @uses GD
954
 * @return resource
955
 */
956
function rotateImageGD($image, $degrees)
957
{
958
	// Kind of need this to do anything
959
	if (function_exists('imagerotate'))
960
	{
961
		// Use positive degrees so GD does not get confused
962
		$degrees -= floor($degrees / 360) * 360;
963
964
		// Rotate away
965
		$background = imagecolorallocatealpha($image, 255, 255, 255, 127);
966
		$image = imagerotate($image, $degrees, $background);
967
	}
968
969
	return $image;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $image also could return the type GdImage which is incompatible with the documented return type resource.
Loading history...
970
}
971
972
/**
973
 * Flop an image using GD functions by copying top to bottom / flop
974
 *
975
 * @param resource $image
976
 * @param array $sizes populated with getimagesize results
977
 *
978
 * @package Graphics
979
 * @uses GD
980
 * @return resource
981
 */
982
function flopImageGD($image, $sizes)
983
{
984
	return flipImageGD($image, $sizes, 'horizontal');
985
}
986
987
/**
988
 * Flip an image using GD function by copying top to bottom / flip vertical
989
 *
990
 * @param resource $image
991
 * @param array $sizes populated with getimagesize results
992
 * @param string $axis vertical for flip about vertical otherwise horizontal flip
993
 *
994
 * @package Graphics
995
 * @uses GD
996
 * @return resource
997
 */
998
function flipImageGD($image, $sizes, $axis = 'vertical')
999
{
1000
	// If the built in function (php 5.5) is available, use it
1001
	if (function_exists('imageflip'))
1002
	{
1003
		imageflip($image, $axis === 'vertical' ? IMG_FLIP_VERTICAL : IMG_FLIP_HORIZONTAL);
1004
	}
1005
	// Pixel mapping then
1006
	else
1007
	{
1008
		$new = imagecreatetruecolor($sizes[0], $sizes[1]);
1009
		imagealphablending($new, false);
1010
		imagesavealpha($new, true);
1011
1012
		if ($axis === 'vertical')
1013
		{
1014
			for ($y = 0; $y < $sizes[1]; $y++)
1015
			{
1016
				imagecopy($new, $image, 0, $y, 0, $sizes[1] - $y - 1, $sizes[0], 1);
1017
			}
1018
		}
1019
		else
1020
		{
1021
			for ($x = 0; $x < $sizes[0]; $x++)
1022
			{
1023
				imagecopy($new, $image, $x, 0, $sizes[0] - $x - 1, 0, 1, $sizes[1]);
1024
			}
1025
		}
1026
1027
		$image = $new;
1028
		unset($new);
1029
	}
1030
1031
	return $image;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $image also could return the type GdImage which is incompatible with the documented return type resource.
Loading history...
1032
}
1033
1034
/**
1035
 * Copy / resize an image using GD bicubic methods
1036
 *
1037
 * What it does:
1038
 *
1039
 * - Used when imagecopyresample() is not available
1040
 * - Uses bicubic resizing methods which are lower quality then imagecopyresample
1041
 *
1042
 * @package Graphics
1043
 * @param resource $dst_img The destination image - a GD image resource
1044
 * @param resource $src_img The source image - a GD image resource
1045
 * @param int $dst_x The "x" coordinate of the destination image
1046
 * @param int $dst_y The "y" coordinate of the destination image
1047
 * @param int $src_x The "x" coordinate of the source image
1048
 * @param int $src_y The "y" coordinate of the source image
1049
 * @param int $dst_w The width of the destination image
1050
 * @param int $dst_h The height of the destination image
1051
 * @param int $src_w The width of the destination image
1052
 * @param int $src_h The height of the destination image
1053
 */
1054
function imagecopyresamplebicubic($dst_img, $src_img, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h)
1055
{
1056
	$palsize = imagecolorstotal($src_img);
1057
	for ($i = 0; $i < $palsize; $i++)
1058
	{
1059
		$colors = imagecolorsforindex($src_img, $i);
1060
		imagecolorallocate($dst_img, $colors['red'], $colors['green'], $colors['blue']);
1061
	}
1062
1063
	$scaleX = ($src_w - 1) / $dst_w;
1064
	$scaleY = ($src_h - 1) / $dst_h;
1065
1066
	$scaleX2 = (int) $scaleX / 2;
1067
	$scaleY2 = (int) $scaleY / 2;
1068
1069
	for ($j = $src_y; $j < $dst_h; $j++)
1070
	{
1071
		$sY = (int) $j * $scaleY;
1072
		$y13 = $sY + $scaleY2;
1073
1074
		for ($i = $src_x; $i < $dst_w; $i++)
1075
		{
1076
			$sX = (int) $i * $scaleX;
1077
			$x34 = $sX + $scaleX2;
1078
1079
			$color1 = imagecolorsforindex($src_img, imagecolorat($src_img, $sX, $y13));
1080
			$color2 = imagecolorsforindex($src_img, imagecolorat($src_img, $sX, $sY));
1081
			$color3 = imagecolorsforindex($src_img, imagecolorat($src_img, $x34, $y13));
1082
			$color4 = imagecolorsforindex($src_img, imagecolorat($src_img, $x34, $sY));
1083
1084
			$red = ($color1['red'] + $color2['red'] + $color3['red'] + $color4['red']) / 4;
1085
			$green = ($color1['green'] + $color2['green'] + $color3['green'] + $color4['green']) / 4;
1086
			$blue = ($color1['blue'] + $color2['blue'] + $color3['blue'] + $color4['blue']) / 4;
1087
1088
			$color = imagecolorresolve($dst_img, $red, $green, $blue);
1089
			if ($color == -1)
1090
			{
1091
				if ($palsize++ < 256)
1092
					imagecolorallocate($dst_img, $red, $green, $blue);
1093
				$color = imagecolorclosest($dst_img, $red, $green, $blue);
1094
			}
1095
1096
			imagesetpixel($dst_img, $i + $dst_x - $src_x, $j + $dst_y - $src_y, $color);
1097
		}
1098
	}
1099
}
1100
1101
if (!function_exists('imagecreatefrombmp'))
1102
{
1103
	/**
1104
	 * It is set only if it doesn't already exist (for forwards compatibility.)
1105
	 *
1106
	 * What it does:
1107
	 *
1108
	 * - It only supports uncompressed bitmaps.
1109
	 * - It only supports standard windows bitmaps (no os/2 variants)
1110
	 * - Returns an image identifier representing the bitmap image
1111
	 * obtained from the given filename.
1112
	 *
1113
	 * @package Graphics
1114
	 * @param string $filename The name of the file
1115
	 * @return resource An image identifier representing the bitmap image
1116
	 */
1117
	function imagecreatefrombmp($filename)
1118
	{
1119
		global $gd2;
1120
1121
		$fp = fopen($filename, 'rb');
1122
1123
		$errors = error_reporting(0);
1124
1125
		// Unpack the general information about the Bitmap Image File, first 14 Bytes
1126
		$header = unpack('vtype/Vsize/Vreserved/Voffset', fread($fp, 14));
1127
1128
		// Unpack the DIB header, it stores detailed information about the bitmap image the pixel format, 40 Bytes long
1129
		$info = unpack('Vsize/Vwidth/Vheight/vplanes/vbits/Vcompression/Vimagesize/Vxres/Vyres/Vncolor/Vcolorimportant', fread($fp, 40));
1130
1131
		// Not a standard bitmap, bail out
1132
		if ($header['type'] != 0x4D42)
1133
			return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type resource.
Loading history...
1134
1135
		// Create our image canvas with the given WxH
1136
		if ($gd2)
1137
			$dst_img = imagecreatetruecolor($info['width'], $info['height']);
1138
		else
1139
			$dst_img = imagecreate($info['width'], $info['height']);
1140
1141
		// Color bitCounts 1,4,8 have palette information we use
1142
		$palette = array();
1143
		if ($info['bits'] == 1 || $info['bits'] == 4 || $info['bits'] == 8)
1144
		{
1145
			$palette_size = $header['offset'] - 54;
1146
1147
			// Read the palette data
1148
			$palettedata = fread($fp, $palette_size);
1149
1150
			// Create the rgb color array
1151
			$n = 0;
1152
			for ($j = 0; $j < $palette_size; $j++)
1153
			{
1154
				$b = ord($palettedata[$j++]);
1155
				$g = ord($palettedata[$j++]);
1156
				$r = ord($palettedata[$j++]);
1157
1158
				$palette[$n++] = imagecolorallocate($dst_img, $r, $g, $b);
1159
			}
1160
		}
1161
1162
		$scan_line_size = ($info['bits'] * $info['width'] + 7) >> 3;
1163
		$scan_line_align = $scan_line_size & 3 ? 4 - ($scan_line_size & 3) : 0;
1164
1165
		for ($y = 0, $l = $info['height'] - 1; $y < $info['height']; $y++, $l--)
1166
		{
1167
			fseek($fp, $header['offset'] + ($scan_line_size + $scan_line_align) * $l);
1168
			$scan_line = fread($fp, $scan_line_size);
1169
1170
			if (strlen($scan_line) < $scan_line_size)
1171
				continue;
1172
1173
			// 32 bits per pixel
1174
			if ($info['bits'] == 32)
1175
			{
1176
				$x = 0;
1177
				for ($j = 0; $j < $scan_line_size; $x++)
1178
				{
1179
					$b = ord($scan_line[$j++]);
1180
					$g = ord($scan_line[$j++]);
1181
					$r = ord($scan_line[$j++]);
1182
					$j++;
1183
1184
					$color = imagecolorexact($dst_img, $r, $g, $b);
1185
					if ($color == -1)
1186
					{
1187
						$color = imagecolorallocate($dst_img, $r, $g, $b);
1188
1189
						// Gah!  Out of colors?  Stupid GD 1... try anyhow.
1190
						if ($color == -1)
1191
							$color = imagecolorclosest($dst_img, $r, $g, $b);
1192
					}
1193
1194
					imagesetpixel($dst_img, $x, $y, $color);
1195
				}
1196
			}
1197
			// 24 bits per pixel
1198
			elseif ($info['bits'] == 24)
1199
			{
1200
				$x = 0;
1201
				for ($j = 0; $j < $scan_line_size; $x++)
1202
				{
1203
					$b = ord($scan_line[$j++]);
1204
					$g = ord($scan_line[$j++]);
1205
					$r = ord($scan_line[$j++]);
1206
1207
					$color = imagecolorexact($dst_img, $r, $g, $b);
1208
					if ($color == -1)
1209
					{
1210
						$color = imagecolorallocate($dst_img, $r, $g, $b);
1211
1212
						// Gah!  Out of colors?  Stupid GD 1... try anyhow.
1213
						if ($color == -1)
1214
							$color = imagecolorclosest($dst_img, $r, $g, $b);
1215
					}
1216
1217
					imagesetpixel($dst_img, $x, $y, $color);
1218
				}
1219
			}
1220
			// 16 bits per pixel
1221
			elseif ($info['bits'] == 16)
1222
			{
1223
				$x = 0;
1224
				for ($j = 0; $j < $scan_line_size; $x++)
1225
				{
1226
					$b1 = ord($scan_line[$j++]);
1227
					$b2 = ord($scan_line[$j++]);
1228
1229
					$word = $b2 * 256 + $b1;
1230
1231
					$b = (($word & 31) * 255) / 31;
1232
					$g = ((($word >> 5) & 31) * 255) / 31;
1233
					$r = ((($word >> 10) & 31) * 255) / 31;
1234
1235
					// Scale the image colors up properly.
1236
					$color = imagecolorexact($dst_img, $r, $g, $b);
1237
					if ($color == -1)
1238
					{
1239
						$color = imagecolorallocate($dst_img, $r, $g, $b);
1240
1241
						// Gah!  Out of colors?  Stupid GD 1... try anyhow.
1242
						if ($color == -1)
1243
							$color = imagecolorclosest($dst_img, $r, $g, $b);
1244
					}
1245
1246
					imagesetpixel($dst_img, $x, $y, $color);
1247
				}
1248
			}
1249
			// 8 bits per pixel
1250
			elseif ($info['bits'] == 8)
1251
			{
1252
				$x = 0;
1253
				for ($j = 0; $j < $scan_line_size; $x++)
1254
					imagesetpixel($dst_img, $x, $y, $palette[ord($scan_line[$j++])]);
1255
			}
1256
			// 4 bits per pixel
1257
			elseif ($info['bits'] == 4)
1258
			{
1259
				$x = 0;
1260
				for ($j = 0; $j < $scan_line_size; $x++)
1261
				{
1262
					$byte = ord($scan_line[$j++]);
1263
1264
					imagesetpixel($dst_img, $x, $y, $palette[(int) ($byte / 16)]);
1265
					if (++$x < $info['width'])
1266
						imagesetpixel($dst_img, $x, $y, $palette[$byte & 15]);
1267
				}
1268
			}
1269
			// 1 bit
1270
			elseif ($info['bits'] == 1)
1271
			{
1272
				$x = 0;
1273
				for ($j = 0; $j < $scan_line_size; $x++)
1274
				{
1275
					$byte = ord($scan_line[$j++]);
1276
1277
					imagesetpixel($dst_img, $x, $y, $palette[(($byte) & 128) != 0]);
1278
					for ($shift = 1; $shift < 8; $shift++)
1279
					{
1280
						if (++$x < $info['width'])
1281
							imagesetpixel($dst_img, $x, $y, $palette[(($byte << $shift) & 128) != 0]);
1282
					}
1283
				}
1284
			}
1285
		}
1286
1287
		fclose($fp);
1288
1289
		error_reporting($errors);
1290
1291
		return $dst_img;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $dst_img also could return the type GdImage which is incompatible with the documented return type resource.
Loading history...
1292
	}
1293
}
1294
1295
/**
1296
 * Show an image containing the visual verification code for registration.
1297
 *
1298
 * What it does:
1299
 *
1300
 * - Requires the GD extension.
1301
 * - Uses a random font for each letter from default_theme_dir/fonts.
1302
 * - Outputs a png if possible, otherwise a gif.
1303
 *
1304
 * @package Graphics
1305
 * @param string $code The code to display
1306
 *
1307
 * @return false|null false if something goes wrong.
1308
 */
1309
function showCodeImage($code)
1310
{
1311
	global $gd2, $settings, $user_info, $modSettings;
1312
1313
	if (!checkGD())
1314
		return false;
1315
1316
	// What type are we going to be doing?
1317
	// Note: The higher the value of visual_verification_type the harder the verification is
1318
	// from 0 as disabled through to 4 as "Very hard".
1319
	$imageType = $modSettings['visual_verification_type'];
1320
1321
	// Special case to allow the admin center to show samples.
1322
	if ($user_info['is_admin'] && isset($_GET['type']))
1323
		$imageType = (int) $_GET['type'];
1324
1325
	// Some quick references for what we do.
1326
	// Do we show no, low or high noise?
1327
	$noiseType = $imageType == 3 ? 'low' : ($imageType == 4 ? 'high' : ($imageType == 5 ? 'extreme' : 'none'));
1328
	// Can we have more than one font in use?
1329
	$varyFonts = $imageType > 1 ? true : false;
1330
	// Just a plain white background?
1331
	$simpleBGColor = $imageType < 3 ? true : false;
1332
	// Plain black foreground?
1333
	$simpleFGColor = $imageType == 0 ? true : false;
1334
	// High much to rotate each character.
1335
	$rotationType = $imageType == 1 ? 'none' : ($imageType > 3 ? 'low' : 'high');
1336
	// Do we show some characters inverse?
1337
	$showReverseChars = $imageType > 3 ? true : false;
1338
	// Special case for not showing any characters.
1339
	$disableChars = $imageType == 0 ? true : false;
1340
	// What do we do with the font colors. Are they one color, close to one color or random?
1341
	$fontColorType = $imageType == 1 ? 'plain' : ($imageType > 3 ? 'random' : 'cyclic');
1342
	// Are the fonts random sizes?
1343
	$fontSizeRandom = $imageType > 3 ? true : false;
1344
	// How much space between characters?
1345
	$fontHorSpace = $imageType > 3 ? 'high' : ($imageType == 1 ? 'medium' : 'minus');
1346
	// Where do characters sit on the image? (Fixed position or random/very random)
1347
	$fontVerPos = $imageType == 1 ? 'fixed' : ($imageType > 3 ? 'vrandom' : 'random');
1348
	// Make font semi-transparent?
1349
	$fontTrans = $imageType == 2 || $imageType == 3 ? true : false;
1350
	// Give the image a border?
1351
	$hasBorder = $simpleBGColor;
1352
1353
	// The amount of pixels in between characters.
1354
	$character_spacing = 1;
1355
1356
	// What color is the background - generally white unless we're on "hard".
1357
	if ($simpleBGColor)
1358
		$background_color = array(255, 255, 255);
1359
	else
1360
		$background_color = isset($settings['verification_background']) ? $settings['verification_background'] : array(236, 237, 243);
1361
1362
	// The color of the characters shown (red, green, blue).
1363
	if ($simpleFGColor)
1364
		$foreground_color = array(0, 0, 0);
1365
	else
1366
	{
1367
		$foreground_color = array(64, 101, 136);
1368
1369
		// Has the theme author requested a custom color?
1370
		if (isset($settings['verification_foreground']))
1371
			$foreground_color = $settings['verification_foreground'];
1372
	}
1373
1374
	if (!is_dir($settings['default_theme_dir'] . '/fonts'))
1375
		return false;
1376
1377
	// Can we use true type fonts?
1378
	$can_do_ttf = function_exists('imagettftext');
1379
1380
	// Get a list of the available fonts.
1381
	$font_dir = dir($settings['default_theme_dir'] . '/fonts');
1382
	$font_list = array();
1383
	$ttfont_list = array();
1384
	while ($entry = $font_dir->read())
1385
	{
1386
		if (preg_match('~^(.+)\.gdf$~', $entry, $matches) === 1)
1387
			$font_list[] = $entry;
1388
		elseif (preg_match('~^(.+)\.ttf$~', $entry, $matches) === 1)
1389
			$ttfont_list[] = $entry;
1390
	}
1391
1392
	if (empty($font_list) && ($can_do_ttf && empty($ttfont_list)))
1393
		return false;
1394
1395
	// For non-hard things don't even change fonts.
1396
	if (!$varyFonts)
1397
	{
1398
		$font_list = !empty($font_list) ? array($font_list[0]) : $font_list;
1399
1400
		// Try use OpenSans if we can - it looks good!
1401
		if (in_array('OpenSans.ttf', $ttfont_list))
1402
			$ttfont_list = array('OpenSans.ttf');
1403
		else
1404
			$ttfont_list = empty($ttfont_list) ? array() : array($ttfont_list[0]);
1405
	}
1406
1407
	// Create a list of characters to be shown.
1408
	$characters = array();
1409
	$loaded_fonts = array();
1410
	$str_len = strlen($code);
1411
	for ($i = 0; $i < $str_len; $i++)
1412
	{
1413
		$characters[$i] = array(
1414
			'id' => $code[$i],
1415
			'font' => array_rand($can_do_ttf ? $ttfont_list : $font_list),
1416
		);
1417
1418
		$loaded_fonts[$characters[$i]['font']] = null;
1419
	}
1420
1421
	// Load all fonts and determine the maximum font height.
1422
	if (!$can_do_ttf)
1423
		foreach ($loaded_fonts as $font_index => $dummy)
1424
			$loaded_fonts[$font_index] = imageloadfont($settings['default_theme_dir'] . '/fonts/' . $font_list[$font_index]);
1425
1426
	// Determine the dimensions of each character.
1427
	$extra = ($imageType == 4 || $imageType == 5) ? 80 : 45;
1428
	$total_width = $character_spacing * strlen($code) + $extra;
1429
	$max_height = 0;
1430
	foreach ($characters as $char_index => $character)
1431
	{
1432
		if ($can_do_ttf)
1433
		{
1434
			// GD2 handles font size differently.
1435
			if ($fontSizeRandom)
1436
				$font_size = $gd2 ? mt_rand(17, 19) : mt_rand(25, 27);
1437
			else
1438
				$font_size = $gd2 ? 17 : 27;
1439
1440
			$img_box = imagettfbbox($font_size, 0, $settings['default_theme_dir'] . '/fonts/' . $ttfont_list[$character['font']], $character['id']);
1441
1442
			$characters[$char_index]['width'] = abs($img_box[2] - $img_box[0]);
1443
			$characters[$char_index]['height'] = abs(max($img_box[1] - $img_box[7], $img_box[5] - $img_box[3]));
1444
		}
1445
		else
1446
		{
1447
			$characters[$char_index]['width'] = imagefontwidth($loaded_fonts[$character['font']]);
1448
			$characters[$char_index]['height'] = imagefontheight($loaded_fonts[$character['font']]);
1449
		}
1450
1451
		$max_height = max($characters[$char_index]['height'] + 10, $max_height);
1452
		$total_width += $characters[$char_index]['width'];
1453
	}
1454
1455
	// Create an image.
1456
	$code_image = $gd2 ? imagecreatetruecolor($total_width, $max_height) : imagecreate($total_width, $max_height);
1457
1458
	// Draw the background.
1459
	$bg_color = imagecolorallocate($code_image, $background_color[0], $background_color[1], $background_color[2]);
1460
	imagefilledrectangle($code_image, 0, 0, $total_width - 1, $max_height - 1, $bg_color);
1461
1462
	// Randomize the foreground color a little.
1463
	for ($i = 0; $i < 3; $i++)
1464
		$foreground_color[$i] = mt_rand(max($foreground_color[$i] - 3, 0), min($foreground_color[$i] + 3, 255));
1465
	$fg_color = imagecolorallocate($code_image, $foreground_color[0], $foreground_color[1], $foreground_color[2]);
1466
1467
	// Color for the noise dots.
1468
	$dotbgcolor = array();
1469
	for ($i = 0; $i < 3; $i++)
1470
		$dotbgcolor[$i] = $background_color[$i] < $foreground_color[$i] ? mt_rand(0, max($foreground_color[$i] - 20, 0)) : mt_rand(min($foreground_color[$i] + 20, 255), 255);
1471
	$randomness_color = imagecolorallocate($code_image, $dotbgcolor[0], $dotbgcolor[1], $dotbgcolor[2]);
1472
1473
	// Some squares/rectangles for new extreme level
1474
	if ($noiseType == 'extreme')
1475
	{
1476
		$width4 = (int) ($total_width / 4);
1477
		$height3 = (int) ($max_height / 3);
1478
		for ($i = 0; $i < mt_rand(1, 5); $i++)
1479
		{
1480
			$x1 = mt_rand(0, $width4);
1481
			$x2 = $x1 + mt_rand($width4, $total_width);
1482
			$y1 = mt_rand(0, $max_height);
1483
			$y2 = $y1 + mt_rand(0, $height3);
1484
			imagefilledrectangle($code_image, $x1, $y1, $x2, $y2, mt_rand(0, 1) ? $fg_color : $randomness_color);
1485
		}
1486
	}
1487
1488
	// Fill in the characters.
1489
	if (!$disableChars)
1490
	{
1491
		$cur_x = 0;
1492
		$last_index = -1;
1493
		foreach ($characters as $char_index => $character)
1494
		{
1495
			// How much rotation will we give?
1496
			if ($rotationType == 'none')
1497
				$angle = 0;
1498
			else
1499
				$angle = mt_rand(-100, 100) / ($rotationType == 'high' ? 6 : 10);
1500
1501
			// What color shall we do it?
1502
			if ($fontColorType == 'cyclic')
1503
			{
1504
				// Here we'll pick from a set of acceptance types.
1505
				$colors = array(
1506
					array(10, 120, 95),
1507
					array(46, 81, 29),
1508
					array(4, 22, 154),
1509
					array(131, 9, 130),
1510
					array(0, 0, 0),
1511
					array(143, 39, 31),
1512
				);
1513
1514
				// Pick a color, but not the same one twice in a row
1515
				$new_index = $last_index;
1516
				while ($last_index == $new_index)
1517
					$new_index = mt_rand(0, count($colors) - 1);
1518
				$char_fg_color = $colors[$new_index];
1519
				$last_index = $new_index;
1520
			}
1521
			elseif ($fontColorType == 'random')
1522
				$char_fg_color = array(mt_rand(max($foreground_color[0] - 2, 0), $foreground_color[0]), mt_rand(max($foreground_color[1] - 2, 0), $foreground_color[1]), mt_rand(max($foreground_color[2] - 2, 0), $foreground_color[2]));
1523
			else
1524
				$char_fg_color = array($foreground_color[0], $foreground_color[1], $foreground_color[2]);
1525
1526
			if (!empty($can_do_ttf))
1527
			{
1528
				// GD2 handles font size differently.
1529
				if ($fontSizeRandom)
1530
					$font_size = $gd2 ? mt_rand(17, 19) : mt_rand(18, 25);
1531
				else
1532
					$font_size = $gd2 ? 18 : 24;
1533
1534
				// Work out the sizes - also fix the character width cause TTF not quite so wide!
1535
				$font_x = $fontHorSpace === 'minus' && $cur_x > 0 ? $cur_x - 3 : $cur_x + 5;
1536
				$font_y = $max_height - ($fontVerPos === 'vrandom' ? mt_rand(2, 8) : ($fontVerPos === 'random' ? mt_rand(3, 5) : 5));
1537
1538
				// What font face?
1539
				if (!empty($ttfont_list))
1540
					$fontface = $settings['default_theme_dir'] . '/fonts/' . $ttfont_list[mt_rand(0, count($ttfont_list) - 1)];
1541
1542
				// What color are we to do it in?
1543
				$is_reverse = $showReverseChars ? mt_rand(0, 1) : false;
1544
				$char_color = $fontTrans ? imagecolorallocatealpha($code_image, $char_fg_color[0], $char_fg_color[1], $char_fg_color[2], 50) : imagecolorallocate($code_image, $char_fg_color[0], $char_fg_color[1], $char_fg_color[2]);
1545
1546
				$fontcord = @imagettftext($code_image, $font_size, $angle, $font_x, $font_y, $char_color, $fontface, $character['id']);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $fontface does not seem to be defined for all execution paths leading up to this point.
Loading history...
1547
				if (empty($fontcord))
1548
					$can_do_ttf = false;
1549
				elseif ($is_reverse !== false)
1550
				{
1551
					if (version_compare(PHP_VERSION, '8.1.0') === -1)
1552
						imagefilledpolygon($code_image, $fontcord, 4, $fg_color);
1553
					else
1554
						imagefilledpolygon($code_image, $fontcord, $fg_color);
0 ignored issues
show
Bug introduced by
The call to imagefilledpolygon() has too few arguments starting with color. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1554
						/** @scrutinizer ignore-call */ 
1555
      imagefilledpolygon($code_image, $fontcord, $fg_color);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
1555
1556
					// Put the character back!
1557
					imagettftext($code_image, $font_size, $angle, $font_x, $font_y, $randomness_color, $fontface, $character['id']);
1558
				}
1559
1560
				if ($can_do_ttf)
1561
					$cur_x = max($fontcord[2], $fontcord[4]) + ($angle == 0 ? 0 : 3);
1562
			}
1563
1564
			if (!$can_do_ttf)
1565
			{
1566
				$char_image = $gd2 ? imagecreatetruecolor($character['width'], $character['height']) : imagecreate($character['width'], $character['height']);
1567
				$char_bgcolor = imagecolorallocate($char_image, $background_color[0], $background_color[1], $background_color[2]);
1568
				imagefilledrectangle($char_image, 0, 0, $character['width'] - 1, $character['height'] - 1, $char_bgcolor);
1569
				imagechar($char_image, $loaded_fonts[$character['font']], 0, 0, $character['id'], imagecolorallocate($char_image, $char_fg_color[0], $char_fg_color[1], $char_fg_color[2]));
1570
				$rotated_char = imagerotate($char_image, mt_rand(-100, 100) / 10, $char_bgcolor);
1571
				imagecopy($code_image, $rotated_char, $cur_x, 0, 0, 0, $character['width'], $character['height']);
1572
				imagedestroy($rotated_char);
1573
				imagedestroy($char_image);
1574
1575
				$cur_x += $character['width'] + $character_spacing;
1576
			}
1577
		}
1578
	}
1579
	// If disabled just show a cross.
1580
	else
1581
	{
1582
		imageline($code_image, 0, 0, $total_width, $max_height, $fg_color);
1583
		imageline($code_image, 0, $max_height, $total_width, 0, $fg_color);
1584
	}
1585
1586
	// Make the background color transparent on the hard image.
1587
	if (!$simpleBGColor)
1588
		imagecolortransparent($code_image, $bg_color);
1589
1590
	if ($hasBorder)
1591
		imagerectangle($code_image, 0, 0, $total_width - 1, $max_height - 1, $fg_color);
1592
1593
	// Add some noise to the background?
1594
	if ($noiseType != 'none')
1595
	{
1596
		for ($i = mt_rand(0, 2); $i < $max_height; $i += mt_rand(1, 2))
1597
			for ($j = mt_rand(0, 10); $j < $total_width; $j += mt_rand(1, 10))
1598
				imagesetpixel($code_image, $j, $i, mt_rand(0, 1) ? $fg_color : $randomness_color);
1599
1600
		// Put in some lines too?
1601
		if ($noiseType != 'extreme')
1602
		{
1603
			$num_lines = $noiseType == 'high' ? mt_rand(3, 7) : mt_rand(2, 5);
1604
			for ($i = 0; $i < $num_lines; $i++)
1605
			{
1606
				if (mt_rand(0, 1))
1607
				{
1608
					$x1 = mt_rand(0, $total_width);
1609
					$x2 = mt_rand(0, $total_width);
1610
					$y1 = 0;
1611
					$y2 = $max_height;
1612
				}
1613
				else
1614
				{
1615
					$y1 = mt_rand(0, $max_height);
1616
					$y2 = mt_rand(0, $max_height);
1617
					$x1 = 0;
1618
					$x2 = $total_width;
1619
				}
1620
				imagesetthickness($code_image, mt_rand(1, 2));
1621
				imageline($code_image, $x1, $y1, $x2, $y2, mt_rand(0, 1) ? $fg_color : $randomness_color);
1622
			}
1623
		}
1624
		else
1625
		{
1626
			// Put in some ellipse
1627
			$num_ellipse = $noiseType == 'extreme' ? mt_rand(6, 12) : mt_rand(2, 6);
1628
			$width4 = (int) ($total_width / 4);
1629
			$width2 = (int) ($total_width / 2);
1630
			$height4 = (int) ($max_height / 4);
1631
			$height2 = (int) ($max_height / 2);
1632
1633
			for ($i = 0; $i < $num_ellipse; $i++)
1634
			{
1635
				$x1 = mt_rand($width4 * -1, $total_width + $width4);
1636
				$x2 = mt_rand($width2, 2 * $total_width);
1637
				$y1 = mt_rand($height4 * -1, $max_height + $height4);
1638
				$y2 = mt_rand($height2, 2 * $max_height);
1639
				imageellipse($code_image, $x1, $y1, $x2, $y2, mt_rand(0, 1) ? $fg_color : $randomness_color);
1640
			}
1641
		}
1642
	}
1643
1644
	// Show the image.
1645
	if (function_exists('imagepng'))
1646
	{
1647
		header('Content-type: image/png');
1648
		imagepng($code_image);
1649
	}
1650
	else
1651
	{
1652
		header('Content-type: image/gif');
1653
		imagegif($code_image);
1654
	}
1655
1656
	// Bail out.
1657
	imagedestroy($code_image);
1658
	die();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
1659
}
1660
1661
/**
1662
 * Show a letter for the visual verification code.
1663
 *
1664
 * - Alternative function for showCodeImage() in case GD is missing.
1665
 * - Includes an image from a random sub directory of default_theme_dir/fonts.
1666
 *
1667
 * @package Graphics
1668
 * @param string $letter A letter to show as an image
1669
 *
1670
 * @return false|null false if something goes wrong.
1671
 */
1672
function showLetterImage($letter)
1673
{
1674
	global $settings;
1675
1676
	if (!is_dir($settings['default_theme_dir'] . '/fonts'))
1677
		return false;
1678
1679
	// Get a list of the available font directories.
1680
	$font_dir = dir($settings['default_theme_dir'] . '/fonts');
1681
	$font_list = array();
1682
	while ($entry = $font_dir->read())
1683
		if ($entry[0] !== '.' && is_dir($settings['default_theme_dir'] . '/fonts/' . $entry) && file_exists($settings['default_theme_dir'] . '/fonts/' . $entry . '.gdf'))
1684
			$font_list[] = $entry;
1685
1686
	if (empty($font_list))
1687
		return false;
1688
1689
	// Pick a random font.
1690
	$random_font = $font_list[array_rand($font_list)];
1691
1692
	// Check if the given letter exists.
1693
	if (!file_exists($settings['default_theme_dir'] . '/fonts/' . $random_font . '/' . $letter . '.gif'))
1694
		return false;
1695
1696
	// Include it!
1697
	header('Content-type: image/gif');
1698
	include($settings['default_theme_dir'] . '/fonts/' . $random_font . '/' . $letter . '.gif');
1699
1700
	// Nothing more to come.
1701
	die();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
1702
}
1703
1704
/**
1705
 * Simple function to generate an image containing some text.
1706
 * It uses preferentially Imagick if present, otherwise GD.
1707
 * Font and size are fixed.
1708
 *
1709
 * @package Graphics
1710
 *
1711
 * @param string $text The text the image should contain
1712
 * @param int $width Width of the final image
1713
 * @param int $height Height of the image
1714
 * @param string $format Type of the image (valid types are png, jpeg, gif)
1715
 *
1716
 * @return boolean|resource The image or false if neither Imagick nor GD are found
1717
 */
1718
function generateTextImage($text, $width = 100, $height = 100, $format = 'png')
1719
{
1720
	$valid_formats = array('jpeg', 'png', 'gif');
1721
	if (!in_array($format, $valid_formats))
1722
	{
1723
		$format = 'png';
1724
	}
1725
1726
	if (checkImagick() === true)
1727
	{
1728
		return generateTextImageWithIM($text, $width, $height, $format);
1729
	}
1730
	elseif (checkGD() === true)
1731
	{
1732
		return generateTextImageWithGD($text, $width, $height, $format);
1733
	}
1734
	else
1735
	{
1736
		return false;
1737
	}
1738
}
1739
1740
/**
1741
 * Simple function to generate an image containing some text.
1742
 * It uses preferentially Imagick if present, otherwise GD.
1743
 * Font and size are fixed.
1744
 *
1745
 * @uses GD
1746
 *
1747
 * @package Graphics
1748
 *
1749
 * @param string $text The text the image should contain
1750
 * @param int $width Width of the final image
1751
 * @param int $height Height of the image
1752
 * @param string $format Type of the image (valid types are png, jpeg, gif)
1753
 *
1754
 * @return resource|boolean The image
1755
 */
1756
function generateTextImageWithGD($text, $width = 100, $height = 100, $format = 'png')
1757
{
1758
	global $settings;
1759
1760
	$create_function = 'image' . $format;
1761
1762
	// Create a white filled box
1763
	$image = imagecreate($width, $height);
1764
	imagecolorallocate($image, 255, 255, 255);
1765
1766
	$text_color = imagecolorallocate($image, 0, 0, 0);
1767
	$font = $settings['default_theme_dir'] . '/fonts/VDS_New.ttf';
1768
1769
	// The loop is to try to fit the text into the image.
1770
	$true_type = function_exists('imagettftext');
1771
	$font_size = $true_type ? 28 : 5;
1772
	do
1773
	{
1774
		if ($true_type)
1775
		{
1776
			$metric = imagettfbbox($font_size, 0, $font, $text);
1777
			$text_width = abs($metric[4] - $metric[0]);
1778
			$text_height = abs($metric[5] - $metric[1]);
1779
			$text_offset = $metric[7];
1780
		}
1781
		else
1782
		{
1783
			$text_width = imagefontwidth($font_size) * strlen($text);
1784
			$text_height = imagefontheight($font_size);
1785
		}
1786
	} while ($text_width > $width && $font_size-- > 1);
1787
1788
	$w_offset = floor(($width - $text_width) / 2);
1789
	$h_offset = floor(($height - $text_height) / 2);
1790
	$h_offset = $true_type ? $h_offset - $text_offset : $h_offset;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $text_offset does not seem to be defined for all execution paths leading up to this point.
Loading history...
1791
1792
	if ($true_type)
1793
	{
1794
		imagettftext($image, $font_size, 0, $w_offset, $h_offset, $text_color, $font, $text);
0 ignored issues
show
Bug introduced by
$h_offset of type double is incompatible with the type integer expected by parameter $y of imagettftext(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1794
		imagettftext($image, $font_size, 0, $w_offset, /** @scrutinizer ignore-type */ $h_offset, $text_color, $font, $text);
Loading history...
Bug introduced by
$w_offset of type double is incompatible with the type integer expected by parameter $x of imagettftext(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1794
		imagettftext($image, $font_size, 0, /** @scrutinizer ignore-type */ $w_offset, $h_offset, $text_color, $font, $text);
Loading history...
1795
	}
1796
	else
1797
	{
1798
		imagestring($image, $font_size, $w_offset, $h_offset, $text, $text_color);
0 ignored issues
show
Bug introduced by
$w_offset of type double is incompatible with the type integer expected by parameter $x of imagestring(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1798
		imagestring($image, $font_size, /** @scrutinizer ignore-type */ $w_offset, $h_offset, $text, $text_color);
Loading history...
Bug introduced by
$h_offset of type double is incompatible with the type integer expected by parameter $y of imagestring(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1798
		imagestring($image, $font_size, $w_offset, /** @scrutinizer ignore-type */ $h_offset, $text, $text_color);
Loading history...
1799
	}
1800
1801
	// Capture the image string
1802
	ob_start();
1803
	$result = $create_function($image);
1804
	$image = ob_get_contents();
1805
	ob_end_clean();
1806
1807
	return $result ? $image : false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result ? $image : false also could return the type string which is incompatible with the documented return type boolean|resource.
Loading history...
1808
}
1809
1810
/**
1811
 * Function to generate an image containing some text.
1812
 * It uses Imagick, Font and size are fixed to fit within width
1813
 *
1814
 * @uses Imagick
1815
 *
1816
 * @package Graphics
1817
 *
1818
 * @param string $text The text the image should contain
1819
 * @param int $width Width of the final image
1820
 * @param int $height Height of the image
1821
 * @param string $format Type of the image (valid types are png, jpeg, gif)
1822
 *
1823
 * @return boolean|resource The image or false on error
1824
 */
1825
function generateTextImageWithIM($text, $width = 100, $height = 100, $format = 'png')
1826
{
1827
	global $settings;
1828
1829
	try
1830
	{
1831
		$image = new Imagick();
1832
		$image->newImage($width, $height, new ImagickPixel('white'));
1833
		$image->setImageFormat($format);
1834
1835
		// 28pt is ~2em given default font stack
1836
		$font_size = 28;
1837
1838
		$draw = new ImagickDraw();
1839
		$draw->setStrokeColor(new ImagickPixel('#000000'));
1840
		$draw->setFillColor(new ImagickPixel('#000000'));
1841
		$draw->setStrokeWidth(0);
1842
		$draw->setTextAlignment(Imagick::ALIGN_CENTER);
1843
		$draw->setFont($settings['default_theme_dir'] . '/fonts/VDS_New.ttf');
1844
1845
		// Make sure the text will fit the the allowed space
1846
		do
1847
		{
1848
			$draw->setFontSize($font_size);
1849
			$metric = $image->queryFontMetrics($draw, $text);
1850
			$text_width = (int) $metric['textWidth'];
1851
		} while ($text_width > $width && $font_size-- > 1);
1852
1853
		// Place text in center of block
1854
		$image->annotateImage($draw, $width / 2, $height / 2 + $font_size / 4, 0, $text);
1855
		$image = $image->getImageBlob();
1856
1857
		return $image;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $image returns the type string which is incompatible with the documented return type boolean|resource.
Loading history...
1858
	}
1859
	catch (Exception $e)
1860
	{
1861
		return false;
1862
	}
1863
}
1864