Passed
Push — master ( d9e5dd...36764d )
by Spuds
01:07 queued 26s
created

hasTransparency()   D

Complexity

Conditions 31
Paths 99

Size

Total Lines 109
Code Lines 52

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 992

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 31
eloc 52
nc 99
nop 2
dl 0
loc 109
ccs 0
cts 63
cp 0
crap 992
rs 4.1666
c 2
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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($type = false)
191
{
192
	$check_im = false;
193
	$check_gd = false;
194
195
	if (checkImagick())
196
	{
197
		$check = Imagick::queryformats();
198
		$check_im = in_array('WEBP', $check);
199
	}
200
201
	if (checkGD())
202
	{
203
		$check = gd_info();
204
		$check_gd = !empty($check['WebP Support']);
205
	}
206
207
	if ($type)
208
	{
209
		return $check_im ? 'im' : ($check_gd ? 'gd' : '');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $check_im ? 'im' : $check_gd ? 'gd' : '' returns the type string which is incompatible with the documented return type boolean.
Loading history...
210
	}
211
212
	return $check_gd || $check_im;
213
}
214
215
/**
216
 * See if we have enough memory to thumbnail an image
217
 *
218
 * @package Graphics
219
 * @param int[] $sizes image size
220
 *
221
 * @return bool Whether or not the memory is available.
222
 */
223
function imageMemoryCheck($sizes)
224
{
225
	global $modSettings;
226
227
	// Just to be sure
228
	if (!is_array($sizes) || $sizes[0] === -1)
0 ignored issues
show
introduced by
The condition is_array($sizes) is always true.
Loading history...
229
	{
230
		return true;
231
	}
232
233
	// Doing the old 'set it and hope' way?
234
	if (empty($modSettings['attachment_thumb_memory']))
235
	{
236
		detectServer()->setMemoryLimit('128M');
237
		return true;
238
	}
239
240
	// Determine the memory requirements for this image, note: if you want to use an image formula
241
	// W x H x bits/8 x channels x Overhead factor
242
	// You will need to account for single bit images as GD expands them to an 8 bit and will greatly
243
	// overun the calculated value.
244
	// The 5 below is simply a shortcut of 8bpp, 3 channels, 1.66 overhead
245
	$needed_memory = ($sizes[0] * $sizes[1] * 5);
246
247
	// If we need more, lets try to get it
248
	return detectServer()->setMemoryLimit($needed_memory, true);
249
}
250
251
/**
252
 * Resize an image from a remote location or a local file.
253
 *
254
 * What it does:
255
 *
256
 * - Puts the resized image at the destination location.
257
 * - The file would have the format preferred_format if possible,
258
 * otherwise the default format is jpeg.
259
 *
260
 * @package Graphics
261
 *
262
 * @param string $source The name of the source image
263
 * @param string $destination The name of the destination image
264
 * @param int $max_width The maximum allowed width
265
 * @param int $max_height The maximum allowed height
266
 * @param int $preferred_format Used by Imagick/resizeImage
267
 * @param bool $strip Allow IM to remove exif data as GD always will
268
 * @param bool $force_resize Always resize the image (force scale up)
269
 *
270
 * @return boolean Whether the thumbnail creation was successful.
271
 */
272
function resizeImageFile($source, $destination, $max_width, $max_height, $preferred_format = 0, $strip = false, $force_resize = true)
273
{
274
	global $modSettings;
275
276
	// Nothing to do without GD or IM
277
	if (!checkGD() && !checkImagick())
278
		return false;
279
280
	if (!file_exists($source) && substr($source, 0, 7) !== 'http://' && substr($source, 0, 8) !== 'https://')
281
	{
282
		return false;
283
	}
284
285
	$default_formats = array(
286
		1 => 'gif',
287
		2 => 'jpeg',
288
		3 => 'png',
289
		6 => 'bmp',
290
		15 => 'wbmp',
291
		18 => 'webp'
292
	);
293
294
	require_once(SUBSDIR . '/Package.subs.php');
295
	require_once(SUBSDIR . '/Attachments.subs.php');
296
297
	// Get the image file, we have to work with something after all
298
	$fp_destination = fopen($destination, 'wb');
299
	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...
300
	{
301
		$fileContents = fetch_web_data($source);
302
303
		fwrite($fp_destination, $fileContents);
304
		fclose($fp_destination);
305
306
		$sizes = elk_getimagesize($destination);
307
	}
308
	elseif ($fp_destination)
0 ignored issues
show
introduced by
$fp_destination is of type resource, thus it always evaluated to false.
Loading history...
309
	{
310
		$sizes = elk_getimagesize($source);
311
312
		$fp_source = fopen($source, 'rb');
313
		if ($fp_source !== false)
314
		{
315
			while (!feof($fp_source))
316
			{
317
				fwrite($fp_destination, fread($fp_source, 8192));
318
			}
319
			fclose($fp_source);
320
		}
321
		else
322
			$sizes = array(-1, -1, -1);
323
324
		fclose($fp_destination);
325
	}
326
	// We can't get to the file.
327
	else
328
		$sizes = array(-1, -1, -1);
329
330
	if ($sizes[0] === -1)
331
		return false;
332
333
	// See if we have -or- can get the needed memory for this operation
334
	if (checkGD() && !imageMemoryCheck($sizes))
335
		return false;
336
337
	$webp_support = hasWebpSupport(true);
338
339
	// Not allowed to save webp or can't support webp input
340
	if ((empty($modSettings['attachment_webp_enable']) && $preferred_format == 18) || ($sizes[2] == 18 && empty($webp_support)))
341
		return false;
342
343
	// A known and supported format?
344
	if (checkImagick() && isset($default_formats[$sizes[2]])
345
		&& ($sizes[2] != 18 || $webp_support === 'im'))
346
	{
347
		return resizeImage(null, $destination, null, null, $max_width, $max_height, $force_resize, $preferred_format, $strip);
348
	}
349
	elseif (checkGD() && isset($default_formats[$sizes[2]])
350
		&& ($sizes[2] != 18 || $webp_support === 'gd')
351
		&& function_exists('imagecreatefrom' . $default_formats[$sizes[2]]))
352
	{
353
		try
354
		{
355
			$imagecreatefrom = 'imagecreatefrom' . $default_formats[$sizes[2]];
356
			$src_img = $imagecreatefrom($destination);
357
			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, true);
0 ignored issues
show
introduced by
The condition $max_height === null is always false.
Loading history...
introduced by
The condition $max_width === null is always false.
Loading history...
358
		}
359
		catch (\Exception $e)
360
		{
361
			return false;
362
		}
363
	}
364
365
	return false;
366
}
367
368
/**
369
 * Resize an image proportionally to fit within the defined max_width and max_height limits
370
 *
371
 * What it does:
372
 *
373
 * - Will do nothing to the image if the file fits within the size limits
374
 * - If Image Magick is present it will use those function over any GD solutions
375
 * - If GD2 is present, it'll use it to achieve better quality (imagecopyresampled)
376
 * - Saves the new image to destination_filename, in the preferred_format
377
 * if possible, default is jpeg.
378
 *
379
 * @uses GD
380
 * @uses Imagick
381
 *
382
 * @package Graphics
383
 * @param resource|null $src_img null for Imagick images, resource form imagecreatefrom for GD
384
 * @param string $destName
385
 * @param int $src_width The width of the source image
386
 * @param int $src_height The height of the source image
387
 * @param int $max_width The maximum allowed width
388
 * @param int $max_height The maximum allowed height
389
 * @param bool $force_resize = false Whether to override defaults and resize it
390
 * @param int $preferred_format - The preferred format
391
 *   - 0 to use jpeg
392
 *   - 1 for gif
393
 *   - 2 to force jpeg
394
 *   - 3 for png
395
 *   - 6 for bmp
396
 *   - 15 for wbmp
397
 *   - 18 for webp
398
 * @param bool $strip Whether to have IM strip EXIF data as GD will
399
 * @param bool $force_gd Whether to force GD processing over IM
400
 *
401
 * @return bool Whether resize was successful.
402
 */
403
function resizeImage($src_img, $destName, $src_width, $src_height, $max_width, $max_height, $force_resize = false, $preferred_format = 0, $strip = false, $force_gd = false)
404
{
405
	global $gd2, $modSettings;
406
407
	if (checkImagick() && !$force_gd)
408
	{
409
		// These are the file formats we know about
410
		$default_formats = array(
411
			1 => 'gif',
412
			2 => 'jpeg',
413
			3 => 'png',
414
			6 => 'bmp',
415
			15 => 'wbmp',
416
			18 => 'webp'
417
		);
418
419
		// Just to be sure, if the admin does not want webp
420
		if (empty($modSettings['attachment_webp_enable']))
421
			unset($default_formats[18]);
422
423
		$preferred_format = empty($preferred_format) || !isset($default_formats[$preferred_format]) ? 2 : $preferred_format;
424
425
		// Since Imagick can throw exceptions, lets catch them
426
		try
427
		{
428
			// Get a new instance of Imagick for use
429
			$imagick = new Imagick($destName);
430
			$imagick->setFirstIterator();
431
432
			// Set the input and output image size
433
			$src_width = empty($src_width) ? $imagick->getImageWidth() : $src_width;
434
			$src_height = empty($src_height) ? $imagick->getImageHeight() : $src_height;
435
436
			// The behavior of bestfit changed in Imagick 3.0.0 and it will now scale up, we prevent that
437
			$dest_width = empty($max_width) ? $src_width : ($force_resize ? $max_width : min($max_width, $src_width));
438
			$dest_height = empty($max_height) ? $src_height : ($force_resize ? $max_height :  min($max_height, $src_height));
439
440
			// Set jpeg image quality to 80
441
			if ($default_formats[$preferred_format] === 'jpeg')
442
			{
443
				$imagick->borderImage('white', 0, 0);
444
				$imagick->setImageCompression(Imagick::COMPRESSION_JPEG);
445
				$imagick->setImageCompressionQuality(80);
446
			}
447
448
			// With PNG Save a few bytes the only way, realistically, we can
449
			if ($default_formats[$preferred_format] === 'png')
450
			{
451
				$imagick->setOption('png:compression-level', '9');
452
				$imagick->setOption('png:exclude-chunk', 'all');
453
			}
454
455
			// Webp
456
			if ($default_formats[$preferred_format] === 'webp')
457
			{
458
				$imagick->setImageCompressionQuality(80);
459
			}
460
461
			// Create a new image in our preferred format and resize it if needed
462
			$imagick->setImageFormat($default_formats[$preferred_format]);
463
			if (substr($destName, -6) === '_thumb')
464
				$imagick->thumbnailImage($dest_width, $dest_height, true);
465
			else
466
				$imagick->resizeImage($dest_width, $dest_height, imagick::FILTER_LANCZOS, 1, true);
467
468
			// Remove EXIF / ICC data?
469
			if ($strip)
470
			{
471
				$imagick->stripImage();
472
			}
473
474
			// Save the new image in the destination location
475
			if ($preferred_format === IMAGETYPE_GIF && $imagick->getNumberImages() !== 0)
476
				$success = $imagick->writeImages($destName, true);
477
			else
478
				$success = $imagick->writeImage($destName);
479
480
			// Free resources associated with the Imagick object
481
			$imagick->clear();
482
		}
483
		catch (Exception $e)
484
		{
485
			$success = false;
486
		}
487
488
		return !empty($success);
489
	}
490
	elseif (checkGD())
491
	{
492
		$success = false;
493
494
		// Determine whether to resize to max width or to max height (depending on the limits.)
495
		if (!empty($max_width) || !empty($max_height))
496
		{
497
			if (!empty($max_width) && (empty($max_height) || $src_height * $max_width / $src_width <= $max_height))
498
			{
499
				$dst_width = $max_width;
500
				$dst_height = floor($src_height * $max_width / $src_width);
501
			}
502
			elseif (!empty($max_height))
503
			{
504
				$dst_width = floor($src_width * $max_height / $src_height);
505
				$dst_height = $max_height;
506
			}
507
508
			// Don't bother resizing if it's already smaller...
509
			if (!empty($dst_width) && !empty($dst_height) && ($dst_width < $src_width || $dst_height < $src_height || $force_resize))
510
			{
511
				// (make a true color image, because it just looks better for resizing.)
512
				if ($gd2)
513
				{
514
					$dst_img = imagecreatetruecolor($dst_width, $dst_height);
515
516
					// Make a true color image, because it just looks better for resizing.
517
					imagesavealpha($dst_img, true);
518
					$color = imagecolorallocatealpha($dst_img, 255, 255, 255, 127);
519
					imagefill($dst_img, 0, 0, $color);
520
521
					// Resize it!
522
					imagecopyresampled($dst_img, $src_img, 0, 0, 0, 0, $dst_width, $dst_height, $src_width, $src_height);
523
				}
524
				else
525
				{
526
					$dst_img = imagecreate($dst_width, $dst_height);
527
					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

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

819
			$source = flopImageGD(/** @scrutinizer ignore-type */ $source, $sizes);
Loading history...
820
			break;
821
		// 3 Normal image, rotated 180
822
		case 3:
823
			$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

823
			$source = rotateImageGD(/** @scrutinizer ignore-type */ $source, 180);
Loading history...
824
			break;
825
		// 4 Mirror image, rotated 180
826
		case 4:
827
			$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

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

1569
						/** @scrutinizer ignore-call */ 
1570
      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...
1570
1571
					// Put the character back!
1572
					imagettftext($code_image, $font_size, $angle, $font_x, $font_y, $randomness_color, $fontface, $character['id']);
1573
				}
1574
1575
				if ($can_do_ttf)
1576
					$cur_x = max($fontcord[2], $fontcord[4]) + ($angle == 0 ? 0 : 3);
1577
			}
1578
1579
			if (!$can_do_ttf)
1580
			{
1581
				$char_image = $gd2 ? imagecreatetruecolor($character['width'], $character['height']) : imagecreate($character['width'], $character['height']);
1582
				$char_bgcolor = imagecolorallocate($char_image, $background_color[0], $background_color[1], $background_color[2]);
1583
				imagefilledrectangle($char_image, 0, 0, $character['width'] - 1, $character['height'] - 1, $char_bgcolor);
1584
				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]));
1585
				$rotated_char = imagerotate($char_image, mt_rand(-100, 100) / 10, $char_bgcolor);
1586
				imagecopy($code_image, $rotated_char, $cur_x, 0, 0, 0, $character['width'], $character['height']);
1587
				imagedestroy($rotated_char);
1588
				imagedestroy($char_image);
1589
1590
				$cur_x += $character['width'] + $character_spacing;
1591
			}
1592
		}
1593
	}
1594
	// If disabled just show a cross.
1595
	else
1596
	{
1597
		imageline($code_image, 0, 0, $total_width, $max_height, $fg_color);
1598
		imageline($code_image, 0, $max_height, $total_width, 0, $fg_color);
1599
	}
1600
1601
	// Make the background color transparent on the hard image.
1602
	if (!$simpleBGColor)
1603
		imagecolortransparent($code_image, $bg_color);
1604
1605
	if ($hasBorder)
1606
		imagerectangle($code_image, 0, 0, $total_width - 1, $max_height - 1, $fg_color);
1607
1608
	// Add some noise to the background?
1609
	if ($noiseType != 'none')
1610
	{
1611
		for ($i = mt_rand(0, 2); $i < $max_height; $i += mt_rand(1, 2))
1612
			for ($j = mt_rand(0, 10); $j < $total_width; $j += mt_rand(1, 10))
1613
				imagesetpixel($code_image, $j, $i, mt_rand(0, 1) ? $fg_color : $randomness_color);
1614
1615
		// Put in some lines too?
1616
		if ($noiseType != 'extreme')
1617
		{
1618
			$num_lines = $noiseType == 'high' ? mt_rand(3, 7) : mt_rand(2, 5);
1619
			for ($i = 0; $i < $num_lines; $i++)
1620
			{
1621
				if (mt_rand(0, 1))
1622
				{
1623
					$x1 = mt_rand(0, $total_width);
1624
					$x2 = mt_rand(0, $total_width);
1625
					$y1 = 0;
1626
					$y2 = $max_height;
1627
				}
1628
				else
1629
				{
1630
					$y1 = mt_rand(0, $max_height);
1631
					$y2 = mt_rand(0, $max_height);
1632
					$x1 = 0;
1633
					$x2 = $total_width;
1634
				}
1635
				imagesetthickness($code_image, mt_rand(1, 2));
1636
				imageline($code_image, $x1, $y1, $x2, $y2, mt_rand(0, 1) ? $fg_color : $randomness_color);
1637
			}
1638
		}
1639
		else
1640
		{
1641
			// Put in some ellipse
1642
			$num_ellipse = $noiseType == 'extreme' ? mt_rand(6, 12) : mt_rand(2, 6);
1643
			$width4 = (int) ($total_width / 4);
1644
			$width2 = (int) ($total_width / 2);
1645
			$height4 = (int) ($max_height / 4);
1646
			$height2 = (int) ($max_height / 2);
1647
1648
			for ($i = 0; $i < $num_ellipse; $i++)
1649
			{
1650
				$x1 = mt_rand($width4 * -1, $total_width + $width4);
1651
				$x2 = mt_rand($width2, 2 * $total_width);
1652
				$y1 = mt_rand($height4 * -1, $max_height + $height4);
1653
				$y2 = mt_rand($height2, 2 * $max_height);
1654
				imageellipse($code_image, $x1, $y1, $x2, $y2, mt_rand(0, 1) ? $fg_color : $randomness_color);
1655
			}
1656
		}
1657
	}
1658
1659
	// Show the image.
1660
	if (function_exists('imagepng'))
1661
	{
1662
		header('Content-type: image/png');
1663
		imagepng($code_image);
1664
	}
1665
	else
1666
	{
1667
		header('Content-type: image/gif');
1668
		imagegif($code_image);
1669
	}
1670
1671
	// Bail out.
1672
	imagedestroy($code_image);
1673
	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...
1674
}
1675
1676
/**
1677
 * Show a letter for the visual verification code.
1678
 *
1679
 * - Alternative function for showCodeImage() in case GD is missing.
1680
 * - Includes an image from a random sub directory of default_theme_dir/fonts.
1681
 *
1682
 * @package Graphics
1683
 * @param string $letter A letter to show as an image
1684
 *
1685
 * @return false|null false if something goes wrong.
1686
 */
1687
function showLetterImage($letter)
1688
{
1689
	global $settings;
1690
1691
	if (!is_dir($settings['default_theme_dir'] . '/fonts'))
1692
		return false;
1693
1694
	// Get a list of the available font directories.
1695
	$font_dir = dir($settings['default_theme_dir'] . '/fonts');
1696
	$font_list = array();
1697
	while ($entry = $font_dir->read())
1698
		if ($entry[0] !== '.' && is_dir($settings['default_theme_dir'] . '/fonts/' . $entry) && file_exists($settings['default_theme_dir'] . '/fonts/' . $entry . '.gdf'))
1699
			$font_list[] = $entry;
1700
1701
	if (empty($font_list))
1702
		return false;
1703
1704
	// Pick a random font.
1705
	$random_font = $font_list[array_rand($font_list)];
1706
1707
	// Check if the given letter exists.
1708
	if (!file_exists($settings['default_theme_dir'] . '/fonts/' . $random_font . '/' . $letter . '.gif'))
1709
		return false;
1710
1711
	// Include it!
1712
	header('Content-type: image/gif');
1713
	include($settings['default_theme_dir'] . '/fonts/' . $random_font . '/' . $letter . '.gif');
1714
1715
	// Nothing more to come.
1716
	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...
1717
}
1718
1719
/**
1720
 * Simple function to generate an image containing some text.
1721
 * It uses preferentially Imagick if present, otherwise GD.
1722
 * Font and size are fixed.
1723
 *
1724
 * @package Graphics
1725
 *
1726
 * @param string $text The text the image should contain
1727
 * @param int $width Width of the final image
1728
 * @param int $height Height of the image
1729
 * @param string $format Type of the image (valid types are png, jpeg, gif)
1730
 *
1731
 * @return boolean|resource The image or false if neither Imagick nor GD are found
1732
 */
1733
function generateTextImage($text, $width = 100, $height = 100, $format = 'png')
1734
{
1735
	$valid_formats = array('jpeg', 'png', 'gif');
1736
	if (!in_array($format, $valid_formats))
1737
	{
1738
		$format = 'png';
1739
	}
1740
1741
	if (checkImagick() === true)
1742
	{
1743
		return generateTextImageWithIM($text, $width, $height, $format);
1744
	}
1745
	elseif (checkGD() === true)
1746
	{
1747
		return generateTextImageWithGD($text, $width, $height, $format);
1748
	}
1749
	else
1750
	{
1751
		return false;
1752
	}
1753
}
1754
1755
/**
1756
 * Simple function to generate an image containing some text.
1757
 * It uses preferentially Imagick if present, otherwise GD.
1758
 * Font and size are fixed.
1759
 *
1760
 * @uses GD
1761
 *
1762
 * @package Graphics
1763
 *
1764
 * @param string $text The text the image should contain
1765
 * @param int $width Width of the final image
1766
 * @param int $height Height of the image
1767
 * @param string $format Type of the image (valid types are png, jpeg, gif)
1768
 *
1769
 * @return resource|boolean The image
1770
 */
1771
function generateTextImageWithGD($text, $width = 100, $height = 100, $format = 'png')
1772
{
1773
	global $settings;
1774
1775
	$create_function = 'image' . $format;
1776
1777
	// Create a white filled box
1778
	$image = imagecreate($width, $height);
1779
	imagecolorallocate($image, 255, 255, 255);
1780
1781
	$text_color = imagecolorallocate($image, 0, 0, 0);
1782
	$font = $settings['default_theme_dir'] . '/fonts/VDS_New.ttf';
1783
1784
	// The loop is to try to fit the text into the image.
1785
	$true_type = function_exists('imagettftext');
1786
	$font_size = $true_type ? 28 : 5;
1787
	do
1788
	{
1789
		if ($true_type)
1790
		{
1791
			$metric = imagettfbbox($font_size, 0, $font, $text);
1792
			$text_width = abs($metric[4] - $metric[0]);
1793
			$text_height = abs($metric[5] - $metric[1]);
1794
			$text_offset = $metric[7];
1795
		}
1796
		else
1797
		{
1798
			$text_width = imagefontwidth($font_size) * strlen($text);
1799
			$text_height = imagefontheight($font_size);
1800
		}
1801
	} while ($text_width > $width && $font_size-- > 1);
1802
1803
	$w_offset = floor(($width - $text_width) / 2);
1804
	$h_offset = floor(($height - $text_height) / 2);
1805
	$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...
1806
1807
	if ($true_type)
1808
	{
1809
		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

1809
		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

1809
		imagettftext($image, $font_size, 0, /** @scrutinizer ignore-type */ $w_offset, $h_offset, $text_color, $font, $text);
Loading history...
1810
	}
1811
	else
1812
	{
1813
		imagestring($image, $font_size, $w_offset, $h_offset, $text, $text_color);
0 ignored issues
show
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

1813
		imagestring($image, $font_size, $w_offset, /** @scrutinizer ignore-type */ $h_offset, $text, $text_color);
Loading history...
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

1813
		imagestring($image, $font_size, /** @scrutinizer ignore-type */ $w_offset, $h_offset, $text, $text_color);
Loading history...
1814
	}
1815
1816
	// Capture the image string
1817
	ob_start();
1818
	$result = $create_function($image);
1819
	$image = ob_get_contents();
1820
	ob_end_clean();
1821
1822
	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...
1823
}
1824
1825
/**
1826
 * Function to generate an image containing some text.
1827
 * It uses Imagick, Font and size are fixed to fit within width
1828
 *
1829
 * @uses Imagick
1830
 *
1831
 * @package Graphics
1832
 *
1833
 * @param string $text The text the image should contain
1834
 * @param int $width Width of the final image
1835
 * @param int $height Height of the image
1836
 * @param string $format Type of the image (valid types are png, jpeg, gif)
1837
 *
1838
 * @return boolean|resource The image or false on error
1839
 */
1840
function generateTextImageWithIM($text, $width = 100, $height = 100, $format = 'png')
1841
{
1842
	global $settings;
1843
1844
	try
1845
	{
1846
		$image = new Imagick();
1847
		$image->newImage($width, $height, new ImagickPixel('white'));
1848
		$image->setImageFormat($format);
1849
1850
		// 28pt is ~2em given default font stack
1851
		$font_size = 28;
1852
1853
		$draw = new ImagickDraw();
1854
		$draw->setStrokeColor(new ImagickPixel('#000000'));
1855
		$draw->setFillColor(new ImagickPixel('#000000'));
1856
		$draw->setStrokeWidth(0);
1857
		$draw->setTextAlignment(Imagick::ALIGN_CENTER);
1858
		$draw->setFont($settings['default_theme_dir'] . '/fonts/VDS_New.ttf');
1859
1860
		// Make sure the text will fit the the allowed space
1861
		do
1862
		{
1863
			$draw->setFontSize($font_size);
1864
			$metric = $image->queryFontMetrics($draw, $text);
1865
			$text_width = (int) $metric['textWidth'];
1866
		} while ($text_width > $width && $font_size-- > 1);
1867
1868
		// Place text in center of block
1869
		$image->annotateImage($draw, $width / 2, $height / 2 + $font_size / 4, 0, $text);
1870
		$image = $image->getImageBlob();
1871
1872
		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...
1873
	}
1874
	catch (Exception $e)
1875
	{
1876
		return false;
1877
	}
1878
}
1879