gif_color_table   A
last analyzed

Complexity

Total Complexity 10

Size/Duplication

Total Lines 67
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 35
dl 0
loc 67
rs 10
c 0
b 0
f 0
wmc 10

4 Methods

Rating   Name   Duplication   Size   Complexity  
A load() 0 16 3
A toString() 0 13 2
A __construct() 0 3 1
A colorIndex() 0 24 4
1
<?php
2
3
/**
4
 * Classes used for reading gif files (in case PHP's GD doesn't provide the
5
 * proper gif-functions).
6
 *
7
 * Gif Util copyright 2003 by Yamasoft (S/C). All rights reserved.
8
 * Do not remove this portion of the header, or use these functions except
9
 * from the original author. To get it, please navigate to:
10
 * http://www.yamasoft.com/php-gif.zip
11
 *
12
 * Simple Machines Forum (SMF)
13
 *
14
 * @package SMF
15
 * @author Simple Machines https://www.simplemachines.org
16
 * @copyright 2022 Simple Machines and individual contributors
17
 * @license https://www.simplemachines.org/about/smf/license.php BSD
18
 *
19
 * @version 2.1.0
20
 */
21
22
if (!defined('SMF'))
23
	die('No direct access...');
24
25
/**
26
 * Class gif_lzw_compression
27
 *
28
 * An implementation of the LZW compression algorithm
29
 */
30
class gif_lzw_compression
31
{
32
	public $MAX_LZW_BITS;
33
	public $Fresh, $CodeSize, $SetCodeSize, $MaxCode, $MaxCodeSize, $FirstCode, $OldCode;
34
	public $ClearCode, $EndCode, $Next, $Vals, $Stack, $sp, $Buf, $CurBit, $LastBit, $Done, $LastByte;
35
36
	public function __construct()
37
	{
38
		$this->MAX_LZW_BITS = 12;
39
		unset($this->Next, $this->Vals, $this->Stack, $this->Buf);
40
41
		$this->Next = range(0, (1 << $this->MAX_LZW_BITS) - 1);
42
		$this->Vals = range(0, (1 << $this->MAX_LZW_BITS) - 1);
43
		$this->Stack = range(0, (1 << ($this->MAX_LZW_BITS + 1)) - 1);
44
		$this->Buf = range(0, 279);
45
	}
46
47
	public function decompress($data, &$datLen)
48
	{
49
		$stLen = strlen($data);
50
		$datLen = 0;
51
		$ret = '';
52
53
		$this->LZWCommand($data, true);
54
55
		while (($iIndex = $this->LZWCommand($data, false)) >= 0)
56
			$ret .= chr($iIndex);
57
58
		$datLen = $stLen - strlen($data);
59
60
		if ($iIndex != -2)
61
			return false;
62
63
		return $ret;
64
	}
65
66
	public function LZWCommand(&$data, $bInit)
67
	{
68
		if ($bInit)
69
		{
70
			$this->SetCodeSize = ord($data[0]);
71
			$data = substr($data, 1);
72
73
			$this->CodeSize = $this->SetCodeSize + 1;
74
			$this->ClearCode = 1 << $this->SetCodeSize;
75
			$this->EndCode = $this->ClearCode + 1;
76
			$this->MaxCode = $this->ClearCode + 2;
77
			$this->MaxCodeSize = $this->ClearCode << 1;
78
79
			$this->GetCode($data, $bInit);
80
81
			$this->Fresh = 1;
82
			for ($i = 0; $i < $this->ClearCode; $i++)
83
			{
84
				$this->Next[$i] = 0;
85
				$this->Vals[$i] = $i;
86
			}
87
88
			for (; $i < (1 << $this->MAX_LZW_BITS); $i++)
89
			{
90
				$this->Next[$i] = 0;
91
				$this->Vals[$i] = 0;
92
			}
93
94
			$this->sp = 0;
95
			return 1;
96
		}
97
98
		if ($this->Fresh)
99
		{
100
			$this->Fresh = 0;
101
			do
102
			{
103
				$this->FirstCode = $this->GetCode($data, $bInit);
104
				$this->OldCode = $this->FirstCode;
105
			}
106
			while ($this->FirstCode == $this->ClearCode);
107
108
			return $this->FirstCode;
109
		}
110
111
		if ($this->sp > 0)
112
		{
113
			$this->sp--;
114
			return $this->Stack[$this->sp];
115
		}
116
117
		while (($Code = $this->GetCode($data, $bInit)) >= 0)
118
		{
119
			if ($Code == $this->ClearCode)
120
			{
121
				for ($i = 0; $i < $this->ClearCode; $i++)
122
				{
123
					$this->Next[$i] = 0;
124
					$this->Vals[$i] = $i;
125
				}
126
127
				for (; $i < (1 << $this->MAX_LZW_BITS); $i++)
128
				{
129
					$this->Next[$i] = 0;
130
					$this->Vals[$i] = 0;
131
				}
132
133
				$this->CodeSize = $this->SetCodeSize + 1;
134
				$this->MaxCodeSize = $this->ClearCode << 1;
135
				$this->MaxCode = $this->ClearCode + 2;
136
				$this->sp = 0;
137
				$this->FirstCode = $this->GetCode($data, $bInit);
138
				$this->OldCode = $this->FirstCode;
139
140
				return $this->FirstCode;
141
			}
142
143
			if ($Code == $this->EndCode)
144
				return -2;
145
146
			$InCode = $Code;
147
			if ($Code >= $this->MaxCode)
148
			{
149
				$this->Stack[$this->sp] = $this->FirstCode;
150
				$this->sp++;
151
				$Code = $this->OldCode;
152
			}
153
154
			while ($Code >= $this->ClearCode)
155
			{
156
				$this->Stack[$this->sp] = $this->Vals[$Code];
157
				$this->sp++;
158
159
				if ($Code == $this->Next[$Code]) // Circular table entry, big GIF Error!
160
					return -1;
161
162
				$Code = $this->Next[$Code];
163
			}
164
165
			$this->FirstCode = $this->Vals[$Code];
166
			$this->Stack[$this->sp] = $this->FirstCode;
167
			$this->sp++;
168
169
			if (($Code = $this->MaxCode) < (1 << $this->MAX_LZW_BITS))
170
			{
171
				$this->Next[$Code] = $this->OldCode;
172
				$this->Vals[$Code] = $this->FirstCode;
173
				$this->MaxCode++;
174
175
				if (($this->MaxCode >= $this->MaxCodeSize) && ($this->MaxCodeSize < (1 << $this->MAX_LZW_BITS)))
176
				{
177
					$this->MaxCodeSize *= 2;
178
					$this->CodeSize++;
179
				}
180
			}
181
182
			$this->OldCode = $InCode;
183
			if ($this->sp > 0)
184
			{
185
				$this->sp--;
186
				return $this->Stack[$this->sp];
187
			}
188
		}
189
190
		return $Code;
191
	}
192
193
	public function GetCode(&$data, $bInit)
194
	{
195
		if ($bInit)
196
		{
197
			$this->CurBit = 0;
198
			$this->LastBit = 0;
199
			$this->Done = 0;
200
			$this->LastByte = 2;
201
202
			return 1;
203
		}
204
205
		if (($this->CurBit + $this->CodeSize) >= $this->LastBit)
206
		{
207
			if ($this->Done)
208
			{
209
				// Ran off the end of my bits...
210
				if ($this->CurBit >= $this->LastBit)
211
					return 0;
212
213
				return -1;
214
			}
215
216
			$this->Buf[0] = $this->Buf[$this->LastByte - 2];
217
			$this->Buf[1] = $this->Buf[$this->LastByte - 1];
218
219
			$count = ord($data[0]);
220
			$data = substr($data, 1);
221
222
			if ($count)
223
			{
224
				for ($i = 0; $i < $count; $i++)
225
					$this->Buf[2 + $i] = ord($data[$i]);
226
227
				$data = substr($data, $count);
228
			}
229
			else
230
				$this->Done = 1;
231
232
			$this->LastByte = 2 + $count;
233
			$this->CurBit = ($this->CurBit - $this->LastBit) + 16;
234
			$this->LastBit = (2 + $count) << 3;
235
		}
236
237
		$iRet = 0;
238
		for ($i = $this->CurBit, $j = 0; $j < $this->CodeSize; $i++, $j++)
239
			$iRet |= (($this->Buf[intval($i / 8)] & (1 << ($i % 8))) != 0) << $j;
240
241
		$this->CurBit += $this->CodeSize;
242
		return $iRet;
243
	}
244
}
245
246
class gif_color_table
247
{
248
	public $m_nColors;
249
	public $m_arColors;
250
251
	public function __construct()
252
	{
253
		unset($this->m_nColors, $this->m_arColors);
254
	}
255
256
	public function load($lpData, $num)
257
	{
258
		$this->m_nColors = 0;
259
		$this->m_arColors = array();
260
261
		for ($i = 0; $i < $num; $i++)
262
		{
263
			$rgb = substr($lpData, $i * 3, 3);
264
			if (strlen($rgb) < 3)
265
				return false;
266
267
			$this->m_arColors[] = (ord($rgb[2]) << 16) + (ord($rgb[1]) << 8) + ord($rgb[0]);
268
			$this->m_nColors++;
269
		}
270
271
		return true;
272
	}
273
274
	public function toString()
275
	{
276
		$ret = '';
277
278
		for ($i = 0; $i < $this->m_nColors; $i++)
279
		{
280
			$ret .=
281
				chr(($this->m_arColors[$i] & 0x000000FF)) . // R
282
				chr(($this->m_arColors[$i] & 0x0000FF00) >> 8) . // G
283
				chr(($this->m_arColors[$i] & 0x00FF0000) >> 16);  // B
284
		}
285
286
		return $ret;
287
	}
288
289
	public function colorIndex($rgb)
290
	{
291
		$dif = 0;
292
		$rgb = intval($rgb) & 0xFFFFFF;
293
		$r1 = ($rgb & 0x0000FF);
294
		$g1 = ($rgb & 0x00FF00) >> 8;
295
		$b1 = ($rgb & 0xFF0000) >> 16;
296
		$idx = -1;
297
298
		for ($i = 0; $i < $this->m_nColors; $i++)
299
		{
300
			$r2 = ($this->m_arColors[$i] & 0x000000FF);
301
			$g2 = ($this->m_arColors[$i] & 0x0000FF00) >> 8;
302
			$b2 = ($this->m_arColors[$i] & 0x00FF0000) >> 16;
303
			$d = abs($r2 - $r1) + abs($g2 - $g1) + abs($b2 - $b1);
304
305
			if (($idx == -1) || ($d < $dif))
306
			{
307
				$idx = $i;
308
				$dif = $d;
309
			}
310
		}
311
312
		return $idx;
313
	}
314
}
315
316
class gif_file_header
317
{
318
	public $m_lpVer, $m_nWidth, $m_nHeight, $m_bGlobalClr, $m_nColorRes;
319
	public $m_bSorted, $m_nTableSize, $m_nBgColor, $m_nPixelRatio;
320
	public $m_colorTable;
321
322
	public function __construct()
323
	{
324
		unset($this->m_lpVer, $this->m_nWidth, $this->m_nHeight, $this->m_bGlobalClr, $this->m_nColorRes);
325
		unset($this->m_bSorted, $this->m_nTableSize, $this->m_nBgColor, $this->m_nPixelRatio, $this->m_colorTable);
326
	}
327
328
	public function load($lpData, &$hdrLen)
329
	{
330
		$hdrLen = 0;
331
332
		$this->m_lpVer = substr($lpData, 0, 6);
333
		if (($this->m_lpVer != 'GIF87a') && ($this->m_lpVer != 'GIF89a'))
334
			return false;
335
336
		list ($this->m_nWidth, $this->m_nHeight) = array_values(unpack('v2', substr($lpData, 6, 4)));
337
338
		if (!$this->m_nWidth || !$this->m_nHeight)
339
			return false;
340
341
		$b = ord(substr($lpData, 10, 1));
342
		$this->m_bGlobalClr = ($b & 0x80) ? true : false;
343
		$this->m_nColorRes = ($b & 0x70) >> 4;
344
		$this->m_bSorted = ($b & 0x08) ? true : false;
345
		$this->m_nTableSize = 2 << ($b & 0x07);
346
		$this->m_nBgColor = ord(substr($lpData, 11, 1));
347
		$this->m_nPixelRatio = ord(substr($lpData, 12, 1));
348
		$hdrLen = 13;
349
350
		if ($this->m_bGlobalClr)
351
		{
352
			$this->m_colorTable = new gif_color_table();
353
			if (!$this->m_colorTable->load(substr($lpData, $hdrLen), $this->m_nTableSize))
354
				return false;
355
356
			$hdrLen += 3 * $this->m_nTableSize;
357
		}
358
359
		return true;
360
	}
361
}
362
363
class gif_image_header
364
{
365
	public $m_nLeft, $m_nTop, $m_nWidth, $m_nHeight, $m_bLocalClr;
366
	public $m_bInterlace, $m_bSorted, $m_nTableSize, $m_colorTable;
367
368
	public function __construct()
369
	{
370
		unset($this->m_nLeft, $this->m_nTop, $this->m_nWidth, $this->m_nHeight, $this->m_bLocalClr);
371
		unset($this->m_bInterlace, $this->m_bSorted, $this->m_nTableSize, $this->m_colorTable);
372
	}
373
374
	public function load($lpData, &$hdrLen)
375
	{
376
		$hdrLen = 0;
377
378
		// Get the width/height/etc. from the header.
379
		list ($this->m_nLeft, $this->m_nTop, $this->m_nWidth, $this->m_nHeight) = array_values(unpack('v4', substr($lpData, 0, 8)));
380
381
		if (!$this->m_nWidth || !$this->m_nHeight)
382
			return false;
383
384
		$b = ord($lpData[8]);
385
		$this->m_bLocalClr = ($b & 0x80) ? true : false;
386
		$this->m_bInterlace = ($b & 0x40) ? true : false;
387
		$this->m_bSorted = ($b & 0x20) ? true : false;
388
		$this->m_nTableSize = 2 << ($b & 0x07);
389
		$hdrLen = 9;
390
391
		if ($this->m_bLocalClr)
392
		{
393
			$this->m_colorTable = new gif_color_table();
394
			if (!$this->m_colorTable->load(substr($lpData, $hdrLen), $this->m_nTableSize))
395
				return false;
396
397
			$hdrLen += 3 * $this->m_nTableSize;
398
		}
399
400
		return true;
401
	}
402
}
403
404
class gif_image
405
{
406
	public $m_disp, $m_bUser, $m_bTrans, $m_nDelay, $m_nTrans, $m_lpComm;
407
	public $m_gih, $m_data, $m_lzw;
408
409
	public function __construct()
410
	{
411
		unset($this->m_disp, $this->m_bUser, $this->m_nDelay, $this->m_nTrans, $this->m_lpComm, $this->m_data);
412
		$this->m_gih = new gif_image_header();
413
		$this->m_lzw = new gif_lzw_compression();
414
	}
415
416
	public function load($data, &$datLen)
417
	{
418
		$datLen = 0;
419
420
		while (true)
421
		{
422
			$b = ord($data[0]);
423
			$data = substr($data, 1);
424
			$datLen++;
425
426
			switch ($b)
427
			{
428
				// Extension...
429
				case 0x21:
430
					$len = 0;
431
					if (!$this->skipExt($data, $len))
432
						return false;
433
434
					$datLen += $len;
435
					break;
436
437
				// Image...
438
				case 0x2C:
439
					// Load the header and color table.
440
					$len = 0;
441
					if (!$this->m_gih->load($data, $len))
442
						return false;
443
444
					$data = substr($data, $len);
445
					$datLen += $len;
446
447
					// Decompress the data, and ride on home ;).
448
					$len = 0;
449
					if (!($this->m_data = $this->m_lzw->decompress($data, $len)))
450
						return false;
451
452
					$datLen += $len;
453
454
					if ($this->m_gih->m_bInterlace)
455
						$this->deInterlace();
456
457
					return true;
458
459
				case 0x3B: // EOF
460
				default:
461
					return false;
462
			}
463
		}
464
		return false;
465
	}
466
467
	public function skipExt(&$data, &$extLen)
468
	{
469
		$extLen = 0;
470
471
		$b = ord($data[0]);
472
		$data = substr($data, 1);
473
		$extLen++;
474
475
		switch ($b)
476
		{
477
			// Graphic Control...
478
			case 0xF9:
479
				$b = ord($data[1]);
480
				$this->m_disp = ($b & 0x1C) >> 2;
481
				$this->m_bUser = ($b & 0x02) ? true : false;
482
				$this->m_bTrans = ($b & 0x01) ? true : false;
483
				list ($this->m_nDelay) = array_values(unpack('v', substr($data, 2, 2)));
484
				$this->m_nTrans = ord($data[4]);
485
				break;
486
487
			// Comment...
488
			case 0xFE:
489
				$this->m_lpComm = substr($data, 1, ord($data[0]));
490
				break;
491
492
			// Plain text...
493
			case 0x01:
494
				break;
495
496
			// Application...
497
			case 0xFF:
498
				break;
499
		}
500
501
		// Skip default as defs may change.
502
		$b = ord($data[0]);
503
		$data = substr($data, 1);
504
		$extLen++;
505
		while ($b > 0)
506
		{
507
			$data = substr($data, $b);
508
			$extLen += $b;
509
			$b = ord($data[0]);
510
			$data = substr($data, 1);
511
			$extLen++;
512
		}
513
		return true;
514
	}
515
516
	public function deInterlace()
517
	{
518
		$data = $this->m_data;
519
520
		for ($i = 0; $i < 4; $i++)
521
		{
522
			switch ($i)
523
			{
524
				case 0:
525
					$s = 8;
526
					$y = 0;
527
					break;
528
529
				case 1:
530
					$s = 8;
531
					$y = 4;
532
					break;
533
534
				case 2:
535
					$s = 4;
536
					$y = 2;
537
					break;
538
539
				case 3:
540
					$s = 2;
541
					$y = 1;
542
					break;
543
			}
544
545
			for (; $y < $this->m_gih->m_nHeight; $y += $s)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $y does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $s does not seem to be defined for all execution paths leading up to this point.
Loading history...
546
			{
547
				$lne = substr($this->m_data, 0, $this->m_gih->m_nWidth);
548
				$this->m_data = substr($this->m_data, $this->m_gih->m_nWidth);
549
550
				$data =
551
					substr($data, 0, $y * $this->m_gih->m_nWidth) .
552
					$lne .
553
					substr($data, ($y + 1) * $this->m_gih->m_nWidth);
554
			}
555
		}
556
557
		$this->m_data = $data;
558
	}
559
}
560
561
class gif_file
562
{
563
	public $header, $image, $data, $loaded;
564
565
	public function __construct()
566
	{
567
		$this->data = '';
568
		$this->loaded = false;
569
		$this->header = new gif_file_header();
570
		$this->image = new gif_image();
571
	}
572
573
	public function loadFile($filename, $iIndex)
574
	{
575
		if ($iIndex < 0)
576
			return false;
577
578
		$this->data = @file_get_contents($filename);
579
		if ($this->data === false)
580
			return false;
581
582
		// Tell the header to load up....
583
		$len = 0;
584
		if (!$this->header->load($this->data, $len))
585
			return false;
586
587
		$this->data = substr($this->data, $len);
588
589
		// Keep reading (at least once) so we get to the actual image we're looking for.
590
		for ($j = 0; $j <= $iIndex; $j++)
591
		{
592
			$imgLen = 0;
593
			if (!$this->image->load($this->data, $imgLen))
594
				return false;
595
596
			$this->data = substr($this->data, $imgLen);
597
		}
598
599
		$this->loaded = true;
600
		return true;
601
	}
602
603
	public function get_png_data($background_color)
604
	{
605
		if (!$this->loaded)
606
			return false;
607
608
		// Prepare the color table.
609
		if ($this->image->m_gih->m_bLocalClr)
610
		{
611
			$colors = $this->image->m_gih->m_nTableSize;
612
			$pal = $this->image->m_gih->m_colorTable->toString();
613
614
			if ($background_color != -1)
615
				$background_color = $this->image->m_gih->m_colorTable->colorIndex($background_color);
616
		}
617
		elseif ($this->header->m_bGlobalClr)
618
		{
619
			$colors = $this->header->m_nTableSize;
620
			$pal = $this->header->m_colorTable->toString();
621
622
			if ($background_color != -1)
623
				$background_color = $this->header->m_colorTable->colorIndex($background_color);
624
		}
625
		else
626
		{
627
			$colors = 0;
628
			$background_color = -1;
629
		}
630
631
		if ($background_color == -1)
632
			$background_color = $this->header->m_nBgColor;
633
634
		$data = &$this->image->m_data;
635
		$header = &$this->image->m_gih;
636
637
		$i = 0;
638
		$bmp = '';
639
640
		// Prepare the bitmap itself.
641
		for ($y = 0; $y < $this->header->m_nHeight; $y++)
642
		{
643
			$bmp .= "\x00";
644
645
			for ($x = 0; $x < $this->header->m_nWidth; $x++, $i++)
646
			{
647
				// Is this in the proper range?  If so, get the specific pixel data...
648
				if ($x >= $header->m_nLeft && $y >= $header->m_nTop && $x < ($header->m_nLeft + $header->m_nWidth) && $y < ($header->m_nTop + $header->m_nHeight))
649
					$bmp .= $data[$i];
650
				// Otherwise, this is background...
651
				else
652
					$bmp .= chr($background_color);
653
			}
654
		}
655
656
		$bmp = gzcompress($bmp, 9);
657
658
		// Output the basic signature first of all.
659
		$out = "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A";
660
661
		// Now, we want the header...
662
		$out .= "\x00\x00\x00\x0D";
663
		$tmp = 'IHDR' . pack('N', (int) $this->header->m_nWidth) . pack('N', (int) $this->header->m_nHeight) . "\x08\x03\x00\x00\x00";
664
		$out .= $tmp . pack('N', smf_crc32($tmp));
665
666
		// The palette, assuming we have one to speak of...
667
		if ($colors > 0)
668
		{
669
			$out .= pack('N', (int) $colors * 3);
670
			$tmp = 'PLTE' . $pal;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $pal does not seem to be defined for all execution paths leading up to this point.
Loading history...
671
			$out .= $tmp . pack('N', smf_crc32($tmp));
672
		}
673
674
		// Do we have any transparency we want to make available?
675
		if ($this->image->m_bTrans && $colors > 0)
676
		{
677
			$out .= pack('N', (int) $colors);
678
			$tmp = 'tRNS';
679
680
			// Stick each color on - full transparency or none.
681
			for ($i = 0; $i < $colors; $i++)
682
				$tmp .= $i == $this->image->m_nTrans ? "\x00" : "\xFF";
683
684
			$out .= $tmp . pack('N', smf_crc32($tmp));
685
		}
686
687
		// Here's the data itself!
688
		$out .= pack('N', strlen($bmp));
689
		$tmp = 'IDAT' . $bmp;
690
		$out .= $tmp . pack('N', smf_crc32($tmp));
691
692
		// EOF marker...
693
		$out .= "\x00\x00\x00\x00" . 'IEND' . "\xAE\x42\x60\x82";
694
695
		return $out;
696
	}
697
}
698
699
// 64-bit only functions?
700
if (!function_exists('smf_crc32'))
701
{
702
	require_once $sourcedir . '/Subs-Compat.php';
703
}
704
705
?>