1<?php
2
3namespace Mpdf\Image;
4
5use Mpdf\Mpdf;
6
7class Bmp
8{
9
10	/**
11	 * @var Mpdf
12	 */
13	private $mpdf;
14
15	public function __construct(Mpdf $mpdf)
16	{
17		$this->mpdf = $mpdf;
18	}
19
20	public function _getBMPimage($data, $file)
21	{
22		// Adapted from script by Valentin Schmidt
23		// http://staff.dasdeck.de/valentin/fpdf/fpdf_bmp/
24		$bfOffBits = $this->_fourbytes2int_le(substr($data, 10, 4));
25		$width = $this->_fourbytes2int_le(substr($data, 18, 4));
26		$height = $this->_fourbytes2int_le(substr($data, 22, 4));
27		$flip = ($height < 0);
28		if ($flip) {
29			$height = -$height;
30		}
31		$biBitCount = $this->_twobytes2int_le(substr($data, 28, 2));
32		$biCompression = $this->_fourbytes2int_le(substr($data, 30, 4));
33		$info = ['w' => $width, 'h' => $height];
34		if ($biBitCount < 16) {
35			$info['cs'] = 'Indexed';
36			$info['bpc'] = $biBitCount;
37			$palStr = substr($data, 54, $bfOffBits - 54);
38			$pal = '';
39			$cnt = strlen($palStr) / 4;
40			for ($i = 0; $i < $cnt; $i++) {
41				$n = 4 * $i;
42				$pal .= $palStr[$n + 2] . $palStr[$n + 1] . $palStr[$n];
43			}
44			$info['pal'] = $pal;
45		} else {
46			$info['cs'] = 'DeviceRGB';
47			$info['bpc'] = 8;
48		}
49
50		if ($this->mpdf->restrictColorSpace == 1 || $this->mpdf->PDFX || $this->mpdf->restrictColorSpace == 3) {
51			if (($this->mpdf->PDFA && !$this->mpdf->PDFAauto) || ($this->mpdf->PDFX && !$this->mpdf->PDFXauto)) {
52				$this->mpdf->PDFAXwarnings[] = "Image cannot be converted to suitable colour space for PDFA or PDFX file - $file - (Image replaced by 'no-image'.)";
53			}
54			return ['error' => "BMP Image cannot be converted to suitable colour space - $file - (Image replaced by 'no-image'.)"];
55		}
56
57		$biXPelsPerMeter = $this->_fourbytes2int_le(substr($data, 38, 4)); // horizontal pixels per meter, usually set to zero
58		//$biYPelsPerMeter=$this->_fourbytes2int_le(substr($data,42,4));	// vertical pixels per meter, usually set to zero
59		$biXPelsPerMeter = round($biXPelsPerMeter / 1000 * 25.4);
60		//$biYPelsPerMeter=round($biYPelsPerMeter/1000 *25.4);
61		$info['set-dpi'] = $biXPelsPerMeter;
62
63		switch ($biCompression) {
64			case 0:
65				$str = substr($data, $bfOffBits);
66				break;
67			case 1: # BI_RLE8
68				$str = $this->rle8_decode(substr($data, $bfOffBits), $width);
69				break;
70			case 2: # BI_RLE4
71				$str = $this->rle4_decode(substr($data, $bfOffBits), $width);
72				break;
73		}
74		$bmpdata = '';
75		$padCnt = (4 - ceil($width / (8 / $biBitCount)) % 4) % 4;
76		switch ($biBitCount) {
77			case 1:
78			case 4:
79			case 8:
80				$w = floor($width / (8 / $biBitCount)) + ($width % (8 / $biBitCount) ? 1 : 0);
81				$w_row = $w + $padCnt;
82				if ($flip) {
83					for ($y = 0; $y < $height; $y++) {
84						$y0 = $y * $w_row;
85						for ($x = 0; $x < $w; $x++) {
86							$bmpdata .= $str[$y0 + $x];
87						}
88					}
89				} else {
90					for ($y = $height - 1; $y >= 0; $y--) {
91						$y0 = $y * $w_row;
92						for ($x = 0; $x < $w; $x++) {
93							$bmpdata .= $str[$y0 + $x];
94						}
95					}
96				}
97				break;
98
99			case 16:
100				$w_row = $width * 2 + $padCnt;
101				if ($flip) {
102					for ($y = 0; $y < $height; $y++) {
103						$y0 = $y * $w_row;
104						for ($x = 0; $x < $width; $x++) {
105							$n = (ord($str[$y0 + 2 * $x + 1]) * 256 + ord($str[$y0 + 2 * $x]));
106							$b = ($n & 31) << 3;
107							$g = ($n & 992) >> 2;
108							$r = ($n & 31744) >> 7;
109							$bmpdata .= chr($r) . chr($g) . chr($b);
110						}
111					}
112				} else {
113					for ($y = $height - 1; $y >= 0; $y--) {
114						$y0 = $y * $w_row;
115						for ($x = 0; $x < $width; $x++) {
116							$n = (ord($str[$y0 + 2 * $x + 1]) * 256 + ord($str[$y0 + 2 * $x]));
117							$b = ($n & 31) << 3;
118							$g = ($n & 992) >> 2;
119							$r = ($n & 31744) >> 7;
120							$bmpdata .= chr($r) . chr($g) . chr($b);
121						}
122					}
123				}
124				break;
125
126			case 24:
127			case 32:
128				$byteCnt = $biBitCount / 8;
129				$w_row = $width * $byteCnt + $padCnt;
130
131				if ($flip) {
132					for ($y = 0; $y < $height; $y++) {
133						$y0 = $y * $w_row;
134						for ($x = 0; $x < $width; $x++) {
135							$i = $y0 + $x * $byteCnt; # + 1
136							$bmpdata .= $str[$i + 2] . $str[$i + 1] . $str[$i];
137						}
138					}
139				} else {
140					for ($y = $height - 1; $y >= 0; $y--) {
141						$y0 = $y * $w_row;
142						for ($x = 0; $x < $width; $x++) {
143							$i = $y0 + $x * $byteCnt; # + 1
144							$bmpdata .= $str[$i + 2] . $str[$i + 1] . $str[$i];
145						}
146					}
147				}
148				break;
149
150			default:
151				return ['error' => 'Error parsing BMP image - Unsupported image biBitCount'];
152		}
153		if ($this->mpdf->compress) {
154			$bmpdata = gzcompress($bmpdata);
155			$info['f'] = 'FlateDecode';
156		}
157		$info['data'] = $bmpdata;
158		$info['type'] = 'bmp';
159		return $info;
160	}
161
162	/**
163	 * Read a 4-byte integer from string
164	 *
165	 * @param $s
166	 * @return int
167	 */
168	private function _fourbytes2int_le($s)
169	{
170		return (ord($s[3]) << 24) + (ord($s[2]) << 16) + (ord($s[1]) << 8) + ord($s[0]);
171	}
172
173	/**
174	 * Read a 2-byte integer from string
175	 *
176	 * @param $s
177	 * @return int
178	 */
179	private function _twobytes2int_le($s)
180	{
181		return (ord(substr($s, 1, 1)) << 8) + ord(substr($s, 0, 1));
182	}
183
184	/**
185	 * Decoder for RLE8 compression in windows bitmaps
186	 *
187	 * @see http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/bitmaps_6x0u.asp
188	 * @param $str
189	 * @param $width
190	 * @return string
191	 */
192	private function rle8_decode($str, $width)
193	{
194		$lineWidth = $width + (3 - ($width - 1) % 4);
195		$out = '';
196		$cnt = strlen($str);
197		for ($i = 0; $i < $cnt; $i++) {
198			$o = ord($str[$i]);
199			if ($o === 0) { # ESCAPE
200				$i++;
201				switch (ord($str[$i])) {
202					case 0: # NEW LINE
203						$padCnt = $lineWidth - strlen($out) % $lineWidth;
204						if ($padCnt < $lineWidth) {
205							$out .= str_repeat(chr(0), $padCnt);# pad line
206						}
207						break;
208					case 1: # END OF FILE
209						$padCnt = $lineWidth - strlen($out) % $lineWidth;
210						if ($padCnt < $lineWidth) {
211							$out .= str_repeat(chr(0), $padCnt);# pad line
212						}
213						break 2;
214					case 2: # DELTA
215						$i += 2;
216						break;
217					default: # ABSOLUTE MODE
218						$num = ord($str[$i]);
219						for ($j = 0; $j < $num; $j++) {
220							$out .= $str[++$i];
221						}
222						if ($num % 2) {
223							$i++;
224						}
225				}
226			} else {
227				$out .= str_repeat($str[++$i], $o);
228			}
229		}
230		return $out;
231	}
232
233	/**
234	 * Decoder for RLE4 compression in windows bitmaps
235	 *
236	 * @see http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/bitmaps_6x0u.asp
237	 * @param $str
238	 * @param $width
239	 * @return string
240	 */
241	private function rle4_decode($str, $width)
242	{
243		$w = floor($width / 2) + ($width % 2);
244		$lineWidth = $w + (3 - ( ($width - 1) / 2) % 4);
245		$pixels = [];
246		$cnt = strlen($str);
247		for ($i = 0; $i < $cnt; $i++) {
248			$o = ord($str[$i]);
249			if ($o === 0) { # ESCAPE
250				$i++;
251				switch (ord($str[$i])) {
252					case 0: # NEW LINE
253						while (count($pixels) % $lineWidth !== 0) {
254							$pixels[] = 0;
255						}
256						break;
257					case 1: # END OF FILE
258						while (count($pixels) % $lineWidth !== 0) {
259							$pixels[] = 0;
260						}
261						break 2;
262					case 2: # DELTA
263						$i += 2;
264						break;
265					default: # ABSOLUTE MODE
266						$num = ord($str[$i]);
267						for ($j = 0; $j < $num; $j++) {
268							if ($j % 2 === 0) {
269								$c = ord($str[++$i]);
270								$pixels[] = ($c & 240) >> 4;
271							} else {
272								$pixels[] = $c & 15; //FIXME: undefined var
273							}
274						}
275						if ($num % 2) {
276							$i++;
277						}
278				}
279			} else {
280				$c = ord($str[++$i]);
281				for ($j = 0; $j < $o; $j++) {
282					$pixels[] = ($j % 2 === 0 ? ($c & 240) >> 4 : $c & 15);
283				}
284			}
285		}
286
287		$out = '';
288		if (count($pixels) % 2) {
289			$pixels[] = 0;
290		}
291		$cnt = count($pixels) / 2;
292		for ($i = 0; $i < $cnt; $i++) {
293			$out .= chr(16 * $pixels[2 * $i] + $pixels[2 * $i + 1]);
294		}
295		return $out;
296	}
297}
298