ILIAS  release_4-3 Revision
 All Data Structures Namespaces Files Functions Variables Groups Pages
module.graphic.bmp.php
Go to the documentation of this file.
1 <?php
4 // available at http://getid3.sourceforge.net //
5 // or http://www.getid3.org //
7 // See readme.txt for more details //
9 // //
10 // module.graphic.bmp.php //
11 // module for analyzing BMP Image files //
12 // dependencies: NONE //
13 // ///
15 
16 
18 {
19 
20  function getid3_bmp(&$fd, &$ThisFileInfo, $ExtractPalette=false, $ExtractData=false) {
21 
22  // shortcuts
23  $ThisFileInfo['bmp']['header']['raw'] = array();
24  $thisfile_bmp = &$ThisFileInfo['bmp'];
25  $thisfile_bmp_header = &$thisfile_bmp['header'];
26  $thisfile_bmp_header_raw = &$thisfile_bmp_header['raw'];
27 
28  // BITMAPFILEHEADER [14 bytes] - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_62uq.asp
29  // all versions
30  // WORD bfType;
31  // DWORD bfSize;
32  // WORD bfReserved1;
33  // WORD bfReserved2;
34  // DWORD bfOffBits;
35 
36  fseek($fd, $ThisFileInfo['avdataoffset'], SEEK_SET);
37  $offset = 0;
38  $BMPheader = fread($fd, 14 + 40);
39 
40  $thisfile_bmp_header_raw['identifier'] = substr($BMPheader, $offset, 2);
41  $offset += 2;
42 
43  if ($thisfile_bmp_header_raw['identifier'] != 'BM') {
44  $ThisFileInfo['error'][] = 'Expecting "BM" at offset '.$ThisFileInfo['avdataoffset'].', found "'.$thisfile_bmp_header_raw['identifier'].'"';
45  unset($ThisFileInfo['fileformat']);
46  unset($ThisFileInfo['bmp']);
47  return false;
48  }
49 
50  $thisfile_bmp_header_raw['filesize'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
51  $offset += 4;
52  $thisfile_bmp_header_raw['reserved1'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
53  $offset += 2;
54  $thisfile_bmp_header_raw['reserved2'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
55  $offset += 2;
56  $thisfile_bmp_header_raw['data_offset'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
57  $offset += 4;
58  $thisfile_bmp_header_raw['header_size'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
59  $offset += 4;
60 
61 
62  // check if the hardcoded-to-1 "planes" is at offset 22 or 26
63  $planes22 = getid3_lib::LittleEndian2Int(substr($BMPheader, 22, 2));
64  $planes26 = getid3_lib::LittleEndian2Int(substr($BMPheader, 26, 2));
65  if (($planes22 == 1) && ($planes26 != 1)) {
66  $thisfile_bmp['type_os'] = 'OS/2';
67  $thisfile_bmp['type_version'] = 1;
68  } elseif (($planes26 == 1) && ($planes22 != 1)) {
69  $thisfile_bmp['type_os'] = 'Windows';
70  $thisfile_bmp['type_version'] = 1;
71  } elseif ($thisfile_bmp_header_raw['header_size'] == 12) {
72  $thisfile_bmp['type_os'] = 'OS/2';
73  $thisfile_bmp['type_version'] = 1;
74  } elseif ($thisfile_bmp_header_raw['header_size'] == 40) {
75  $thisfile_bmp['type_os'] = 'Windows';
76  $thisfile_bmp['type_version'] = 1;
77  } elseif ($thisfile_bmp_header_raw['header_size'] == 84) {
78  $thisfile_bmp['type_os'] = 'Windows';
79  $thisfile_bmp['type_version'] = 4;
80  } elseif ($thisfile_bmp_header_raw['header_size'] == 100) {
81  $thisfile_bmp['type_os'] = 'Windows';
82  $thisfile_bmp['type_version'] = 5;
83  } else {
84  $ThisFileInfo['error'][] = 'Unknown BMP subtype (or not a BMP file)';
85  unset($ThisFileInfo['fileformat']);
86  unset($ThisFileInfo['bmp']);
87  return false;
88  }
89 
90  $ThisFileInfo['fileformat'] = 'bmp';
91  $ThisFileInfo['video']['dataformat'] = 'bmp';
92  $ThisFileInfo['video']['lossless'] = true;
93  $ThisFileInfo['video']['pixel_aspect_ratio'] = (float) 1;
94 
95  if ($thisfile_bmp['type_os'] == 'OS/2') {
96 
97  // OS/2-format BMP
98  // http://netghost.narod.ru/gff/graphics/summary/os2bmp.htm
99 
100  // DWORD Size; /* Size of this structure in bytes */
101  // DWORD Width; /* Bitmap width in pixels */
102  // DWORD Height; /* Bitmap height in pixel */
103  // WORD NumPlanes; /* Number of bit planes (color depth) */
104  // WORD BitsPerPixel; /* Number of bits per pixel per plane */
105 
106  $thisfile_bmp_header_raw['width'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
107  $offset += 2;
108  $thisfile_bmp_header_raw['height'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
109  $offset += 2;
110  $thisfile_bmp_header_raw['planes'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
111  $offset += 2;
112  $thisfile_bmp_header_raw['bits_per_pixel'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
113  $offset += 2;
114 
115  $ThisFileInfo['video']['resolution_x'] = $thisfile_bmp_header_raw['width'];
116  $ThisFileInfo['video']['resolution_y'] = $thisfile_bmp_header_raw['height'];
117  $ThisFileInfo['video']['codec'] = 'BI_RGB '.$thisfile_bmp_header_raw['bits_per_pixel'].'-bit';
118  $ThisFileInfo['video']['bits_per_sample'] = $thisfile_bmp_header_raw['bits_per_pixel'];
119 
120  if ($thisfile_bmp['type_version'] >= 2) {
121  // DWORD Compression; /* Bitmap compression scheme */
122  // DWORD ImageDataSize; /* Size of bitmap data in bytes */
123  // DWORD XResolution; /* X resolution of display device */
124  // DWORD YResolution; /* Y resolution of display device */
125  // DWORD ColorsUsed; /* Number of color table indices used */
126  // DWORD ColorsImportant; /* Number of important color indices */
127  // WORD Units; /* Type of units used to measure resolution */
128  // WORD Reserved; /* Pad structure to 4-byte boundary */
129  // WORD Recording; /* Recording algorithm */
130  // WORD Rendering; /* Halftoning algorithm used */
131  // DWORD Size1; /* Reserved for halftoning algorithm use */
132  // DWORD Size2; /* Reserved for halftoning algorithm use */
133  // DWORD ColorEncoding; /* Color model used in bitmap */
134  // DWORD Identifier; /* Reserved for application use */
135 
136  $thisfile_bmp_header_raw['compression'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
137  $offset += 4;
138  $thisfile_bmp_header_raw['bmp_data_size'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
139  $offset += 4;
140  $thisfile_bmp_header_raw['resolution_h'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
141  $offset += 4;
142  $thisfile_bmp_header_raw['resolution_v'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
143  $offset += 4;
144  $thisfile_bmp_header_raw['colors_used'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
145  $offset += 4;
146  $thisfile_bmp_header_raw['colors_important'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
147  $offset += 4;
148  $thisfile_bmp_header_raw['resolution_units'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
149  $offset += 2;
150  $thisfile_bmp_header_raw['reserved1'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
151  $offset += 2;
152  $thisfile_bmp_header_raw['recording'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
153  $offset += 2;
154  $thisfile_bmp_header_raw['rendering'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
155  $offset += 2;
156  $thisfile_bmp_header_raw['size1'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
157  $offset += 4;
158  $thisfile_bmp_header_raw['size2'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
159  $offset += 4;
160  $thisfile_bmp_header_raw['color_encoding'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
161  $offset += 4;
162  $thisfile_bmp_header_raw['identifier'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
163  $offset += 4;
164 
165  $thisfile_bmp_header['compression'] = $this->BMPcompressionOS2Lookup($thisfile_bmp_header_raw['compression']);
166 
167  $ThisFileInfo['video']['codec'] = $thisfile_bmp_header['compression'].' '.$thisfile_bmp_header_raw['bits_per_pixel'].'-bit';
168  }
169 
170  } elseif ($thisfile_bmp['type_os'] == 'Windows') {
171 
172  // Windows-format BMP
173 
174  // BITMAPINFOHEADER - [40 bytes] http://msdn.microsoft.com/library/en-us/gdi/bitmaps_1rw2.asp
175  // all versions
176  // DWORD biSize;
177  // LONG biWidth;
178  // LONG biHeight;
179  // WORD biPlanes;
180  // WORD biBitCount;
181  // DWORD biCompression;
182  // DWORD biSizeImage;
183  // LONG biXPelsPerMeter;
184  // LONG biYPelsPerMeter;
185  // DWORD biClrUsed;
186  // DWORD biClrImportant;
187 
188  $thisfile_bmp_header_raw['width'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4), true);
189  $offset += 4;
190  $thisfile_bmp_header_raw['height'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4), true);
191  $offset += 4;
192  $thisfile_bmp_header_raw['planes'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
193  $offset += 2;
194  $thisfile_bmp_header_raw['bits_per_pixel'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 2));
195  $offset += 2;
196  $thisfile_bmp_header_raw['compression'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
197  $offset += 4;
198  $thisfile_bmp_header_raw['bmp_data_size'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
199  $offset += 4;
200  $thisfile_bmp_header_raw['resolution_h'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4), true);
201  $offset += 4;
202  $thisfile_bmp_header_raw['resolution_v'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4), true);
203  $offset += 4;
204  $thisfile_bmp_header_raw['colors_used'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
205  $offset += 4;
206  $thisfile_bmp_header_raw['colors_important'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
207  $offset += 4;
208 
209  $thisfile_bmp_header['compression'] = $this->BMPcompressionWindowsLookup($thisfile_bmp_header_raw['compression']);
210  $ThisFileInfo['video']['resolution_x'] = $thisfile_bmp_header_raw['width'];
211  $ThisFileInfo['video']['resolution_y'] = $thisfile_bmp_header_raw['height'];
212  $ThisFileInfo['video']['codec'] = $thisfile_bmp_header['compression'].' '.$thisfile_bmp_header_raw['bits_per_pixel'].'-bit';
213  $ThisFileInfo['video']['bits_per_sample'] = $thisfile_bmp_header_raw['bits_per_pixel'];
214 
215  if (($thisfile_bmp['type_version'] >= 4) || ($thisfile_bmp_header_raw['compression'] == 3)) {
216  // should only be v4+, but BMPs with type_version==1 and BI_BITFIELDS compression have been seen
217  $BMPheader .= fread($fd, 44);
218 
219  // BITMAPV4HEADER - [44 bytes] - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_2k1e.asp
220  // Win95+, WinNT4.0+
221  // DWORD bV4RedMask;
222  // DWORD bV4GreenMask;
223  // DWORD bV4BlueMask;
224  // DWORD bV4AlphaMask;
225  // DWORD bV4CSType;
226  // CIEXYZTRIPLE bV4Endpoints;
227  // DWORD bV4GammaRed;
228  // DWORD bV4GammaGreen;
229  // DWORD bV4GammaBlue;
230  $thisfile_bmp_header_raw['red_mask'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
231  $offset += 4;
232  $thisfile_bmp_header_raw['green_mask'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
233  $offset += 4;
234  $thisfile_bmp_header_raw['blue_mask'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
235  $offset += 4;
236  $thisfile_bmp_header_raw['alpha_mask'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
237  $offset += 4;
238  $thisfile_bmp_header_raw['cs_type'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
239  $offset += 4;
240  $thisfile_bmp_header_raw['ciexyz_red'] = substr($BMPheader, $offset, 4);
241  $offset += 4;
242  $thisfile_bmp_header_raw['ciexyz_green'] = substr($BMPheader, $offset, 4);
243  $offset += 4;
244  $thisfile_bmp_header_raw['ciexyz_blue'] = substr($BMPheader, $offset, 4);
245  $offset += 4;
246  $thisfile_bmp_header_raw['gamma_red'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
247  $offset += 4;
248  $thisfile_bmp_header_raw['gamma_green'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
249  $offset += 4;
250  $thisfile_bmp_header_raw['gamma_blue'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
251  $offset += 4;
252 
253  $thisfile_bmp_header['ciexyz_red'] = getid3_lib::FixedPoint2_30(strrev($thisfile_bmp_header_raw['ciexyz_red']));
254  $thisfile_bmp_header['ciexyz_green'] = getid3_lib::FixedPoint2_30(strrev($thisfile_bmp_header_raw['ciexyz_green']));
255  $thisfile_bmp_header['ciexyz_blue'] = getid3_lib::FixedPoint2_30(strrev($thisfile_bmp_header_raw['ciexyz_blue']));
256  }
257 
258  if ($thisfile_bmp['type_version'] >= 5) {
259  $BMPheader .= fread($fd, 16);
260 
261  // BITMAPV5HEADER - [16 bytes] - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_7c36.asp
262  // Win98+, Win2000+
263  // DWORD bV5Intent;
264  // DWORD bV5ProfileData;
265  // DWORD bV5ProfileSize;
266  // DWORD bV5Reserved;
267  $thisfile_bmp_header_raw['intent'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
268  $offset += 4;
269  $thisfile_bmp_header_raw['profile_data_offset'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
270  $offset += 4;
271  $thisfile_bmp_header_raw['profile_data_size'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
272  $offset += 4;
273  $thisfile_bmp_header_raw['reserved3'] = getid3_lib::LittleEndian2Int(substr($BMPheader, $offset, 4));
274  $offset += 4;
275  }
276 
277  } else {
278 
279  $ThisFileInfo['error'][] = 'Unknown BMP format in header.';
280  return false;
281 
282  }
283 
284 
285  if ($ExtractPalette || $ExtractData) {
286  $PaletteEntries = 0;
287  if ($thisfile_bmp_header_raw['bits_per_pixel'] < 16) {
288  $PaletteEntries = pow(2, $thisfile_bmp_header_raw['bits_per_pixel']);
289  } elseif (isset($thisfile_bmp_header_raw['colors_used']) && ($thisfile_bmp_header_raw['colors_used'] > 0) && ($thisfile_bmp_header_raw['colors_used'] <= 256)) {
290  $PaletteEntries = $thisfile_bmp_header_raw['colors_used'];
291  }
292  if ($PaletteEntries > 0) {
293  $BMPpalette = fread($fd, 4 * $PaletteEntries);
294  $paletteoffset = 0;
295  for ($i = 0; $i < $PaletteEntries; $i++) {
296  // RGBQUAD - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_5f8y.asp
297  // BYTE rgbBlue;
298  // BYTE rgbGreen;
299  // BYTE rgbRed;
300  // BYTE rgbReserved;
301  $blue = getid3_lib::LittleEndian2Int(substr($BMPpalette, $paletteoffset++, 1));
302  $green = getid3_lib::LittleEndian2Int(substr($BMPpalette, $paletteoffset++, 1));
303  $red = getid3_lib::LittleEndian2Int(substr($BMPpalette, $paletteoffset++, 1));
304  if (($thisfile_bmp['type_os'] == 'OS/2') && ($thisfile_bmp['type_version'] == 1)) {
305  // no padding byte
306  } else {
307  $paletteoffset++; // padding byte
308  }
309  $thisfile_bmp['palette'][$i] = (($red << 16) | ($green << 8) | $blue);
310  }
311  }
312  }
313 
314  if ($ExtractData) {
315  fseek($fd, $thisfile_bmp_header_raw['data_offset'], SEEK_SET);
316  $RowByteLength = ceil(($thisfile_bmp_header_raw['width'] * ($thisfile_bmp_header_raw['bits_per_pixel'] / 8)) / 4) * 4; // round up to nearest DWORD boundry
317  $BMPpixelData = fread($fd, $thisfile_bmp_header_raw['height'] * $RowByteLength);
318  $pixeldataoffset = 0;
319  switch (@$thisfile_bmp_header_raw['compression']) {
320 
321  case 0: // BI_RGB
322  switch ($thisfile_bmp_header_raw['bits_per_pixel']) {
323  case 1:
324  for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
325  for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col = $col) {
326  $paletteindexbyte = ord($BMPpixelData{$pixeldataoffset++});
327  for ($i = 7; $i >= 0; $i--) {
328  $paletteindex = ($paletteindexbyte & (0x01 << $i)) >> $i;
329  $thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex];
330  $col++;
331  }
332  }
333  while (($pixeldataoffset % 4) != 0) {
334  // lines are padded to nearest DWORD
335  $pixeldataoffset++;
336  }
337  }
338  break;
339 
340  case 4:
341  for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
342  for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col = $col) {
343  $paletteindexbyte = ord($BMPpixelData{$pixeldataoffset++});
344  for ($i = 1; $i >= 0; $i--) {
345  $paletteindex = ($paletteindexbyte & (0x0F << (4 * $i))) >> (4 * $i);
346  $thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex];
347  $col++;
348  }
349  }
350  while (($pixeldataoffset % 4) != 0) {
351  // lines are padded to nearest DWORD
352  $pixeldataoffset++;
353  }
354  }
355  break;
356 
357  case 8:
358  for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
359  for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col++) {
360  $paletteindex = ord($BMPpixelData{$pixeldataoffset++});
361  $thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex];
362  }
363  while (($pixeldataoffset % 4) != 0) {
364  // lines are padded to nearest DWORD
365  $pixeldataoffset++;
366  }
367  }
368  break;
369 
370  case 24:
371  for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
372  for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col++) {
373  $thisfile_bmp['data'][$row][$col] = (ord($BMPpixelData{$pixeldataoffset+2}) << 16) | (ord($BMPpixelData{$pixeldataoffset+1}) << 8) | ord($BMPpixelData{$pixeldataoffset});
374  $pixeldataoffset += 3;
375  }
376  while (($pixeldataoffset % 4) != 0) {
377  // lines are padded to nearest DWORD
378  $pixeldataoffset++;
379  }
380  }
381  break;
382 
383  case 32:
384  for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
385  for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col++) {
386  $thisfile_bmp['data'][$row][$col] = (ord($BMPpixelData{$pixeldataoffset+3}) << 24) | (ord($BMPpixelData{$pixeldataoffset+2}) << 16) | (ord($BMPpixelData{$pixeldataoffset+1}) << 8) | ord($BMPpixelData{$pixeldataoffset});
387  $pixeldataoffset += 4;
388  }
389  while (($pixeldataoffset % 4) != 0) {
390  // lines are padded to nearest DWORD
391  $pixeldataoffset++;
392  }
393  }
394  break;
395 
396  case 16:
397  // ?
398  break;
399 
400  default:
401  $ThisFileInfo['error'][] = 'Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data';
402  break;
403  }
404  break;
405 
406 
407  case 1: // BI_RLE8 - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_6x0u.asp
408  switch ($thisfile_bmp_header_raw['bits_per_pixel']) {
409  case 8:
410  $pixelcounter = 0;
411  while ($pixeldataoffset < strlen($BMPpixelData)) {
412  $firstbyte = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
413  $secondbyte = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
414  if ($firstbyte == 0) {
415 
416  // escaped/absolute mode - the first byte of the pair can be set to zero to
417  // indicate an escape character that denotes the end of a line, the end of
418  // a bitmap, or a delta, depending on the value of the second byte.
419  switch ($secondbyte) {
420  case 0:
421  // end of line
422  // no need for special processing, just ignore
423  break;
424 
425  case 1:
426  // end of bitmap
427  $pixeldataoffset = strlen($BMPpixelData); // force to exit loop just in case
428  break;
429 
430  case 2:
431  // delta - The 2 bytes following the escape contain unsigned values
432  // indicating the horizontal and vertical offsets of the next pixel
433  // from the current position.
434  $colincrement = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
435  $rowincrement = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
436  $col = ($pixelcounter % $thisfile_bmp_header_raw['width']) + $colincrement;
437  $row = ($thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width'])) - $rowincrement;
438  $pixelcounter = ($row * $thisfile_bmp_header_raw['width']) + $col;
439  break;
440 
441  default:
442  // In absolute mode, the first byte is zero and the second byte is a
443  // value in the range 03H through FFH. The second byte represents the
444  // number of bytes that follow, each of which contains the color index
445  // of a single pixel. Each run must be aligned on a word boundary.
446  for ($i = 0; $i < $secondbyte; $i++) {
447  $paletteindex = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
448  $col = $pixelcounter % $thisfile_bmp_header_raw['width'];
449  $row = $thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width']);
450  $thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex];
451  $pixelcounter++;
452  }
453  while (($pixeldataoffset % 2) != 0) {
454  // Each run must be aligned on a word boundary.
455  $pixeldataoffset++;
456  }
457  break;
458  }
459 
460  } else {
461 
462  // encoded mode - the first byte specifies the number of consecutive pixels
463  // to be drawn using the color index contained in the second byte.
464  for ($i = 0; $i < $firstbyte; $i++) {
465  $col = $pixelcounter % $thisfile_bmp_header_raw['width'];
466  $row = $thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width']);
467  $thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$secondbyte];
468  $pixelcounter++;
469  }
470 
471  }
472  }
473  break;
474 
475  default:
476  $ThisFileInfo['error'][] = 'Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data';
477  break;
478  }
479  break;
480 
481 
482 
483  case 2: // BI_RLE4 - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_6x0u.asp
484  switch ($thisfile_bmp_header_raw['bits_per_pixel']) {
485  case 4:
486  $pixelcounter = 0;
487  while ($pixeldataoffset < strlen($BMPpixelData)) {
488  $firstbyte = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
489  $secondbyte = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
490  if ($firstbyte == 0) {
491 
492  // escaped/absolute mode - the first byte of the pair can be set to zero to
493  // indicate an escape character that denotes the end of a line, the end of
494  // a bitmap, or a delta, depending on the value of the second byte.
495  switch ($secondbyte) {
496  case 0:
497  // end of line
498  // no need for special processing, just ignore
499  break;
500 
501  case 1:
502  // end of bitmap
503  $pixeldataoffset = strlen($BMPpixelData); // force to exit loop just in case
504  break;
505 
506  case 2:
507  // delta - The 2 bytes following the escape contain unsigned values
508  // indicating the horizontal and vertical offsets of the next pixel
509  // from the current position.
510  $colincrement = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
511  $rowincrement = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
512  $col = ($pixelcounter % $thisfile_bmp_header_raw['width']) + $colincrement;
513  $row = ($thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width'])) - $rowincrement;
514  $pixelcounter = ($row * $thisfile_bmp_header_raw['width']) + $col;
515  break;
516 
517  default:
518  // In absolute mode, the first byte is zero. The second byte contains the number
519  // of color indexes that follow. Subsequent bytes contain color indexes in their
520  // high- and low-order 4 bits, one color index for each pixel. In absolute mode,
521  // each run must be aligned on a word boundary.
522  unset($paletteindexes);
523  for ($i = 0; $i < ceil($secondbyte / 2); $i++) {
524  $paletteindexbyte = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
525  $paletteindexes[] = ($paletteindexbyte & 0xF0) >> 4;
526  $paletteindexes[] = ($paletteindexbyte & 0x0F);
527  }
528  while (($pixeldataoffset % 2) != 0) {
529  // Each run must be aligned on a word boundary.
530  $pixeldataoffset++;
531  }
532 
533  foreach ($paletteindexes as $paletteindex) {
534  $col = $pixelcounter % $thisfile_bmp_header_raw['width'];
535  $row = $thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width']);
536  $thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex];
537  $pixelcounter++;
538  }
539  break;
540  }
541 
542  } else {
543 
544  // encoded mode - the first byte of the pair contains the number of pixels to be
545  // drawn using the color indexes in the second byte. The second byte contains two
546  // color indexes, one in its high-order 4 bits and one in its low-order 4 bits.
547  // The first of the pixels is drawn using the color specified by the high-order
548  // 4 bits, the second is drawn using the color in the low-order 4 bits, the third
549  // is drawn using the color in the high-order 4 bits, and so on, until all the
550  // pixels specified by the first byte have been drawn.
551  $paletteindexes[0] = ($secondbyte & 0xF0) >> 4;
552  $paletteindexes[1] = ($secondbyte & 0x0F);
553  for ($i = 0; $i < $firstbyte; $i++) {
554  $col = $pixelcounter % $thisfile_bmp_header_raw['width'];
555  $row = $thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width']);
556  $thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindexes[($i % 2)]];
557  $pixelcounter++;
558  }
559 
560  }
561  }
562  break;
563 
564  default:
565  $ThisFileInfo['error'][] = 'Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data';
566  break;
567  }
568  break;
569 
570 
571  case 3: // BI_BITFIELDS
572  switch ($thisfile_bmp_header_raw['bits_per_pixel']) {
573  case 16:
574  case 32:
575  $redshift = 0;
576  $greenshift = 0;
577  $blueshift = 0;
578  while ((($thisfile_bmp_header_raw['red_mask'] >> $redshift) & 0x01) == 0) {
579  $redshift++;
580  }
581  while ((($thisfile_bmp_header_raw['green_mask'] >> $greenshift) & 0x01) == 0) {
582  $greenshift++;
583  }
584  while ((($thisfile_bmp_header_raw['blue_mask'] >> $blueshift) & 0x01) == 0) {
585  $blueshift++;
586  }
587  for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
588  for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col++) {
589  $pixelvalue = getid3_lib::LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset, $thisfile_bmp_header_raw['bits_per_pixel'] / 8));
590  $pixeldataoffset += $thisfile_bmp_header_raw['bits_per_pixel'] / 8;
591 
592  $red = intval(round(((($pixelvalue & $thisfile_bmp_header_raw['red_mask']) >> $redshift) / ($thisfile_bmp_header_raw['red_mask'] >> $redshift)) * 255));
593  $green = intval(round(((($pixelvalue & $thisfile_bmp_header_raw['green_mask']) >> $greenshift) / ($thisfile_bmp_header_raw['green_mask'] >> $greenshift)) * 255));
594  $blue = intval(round(((($pixelvalue & $thisfile_bmp_header_raw['blue_mask']) >> $blueshift) / ($thisfile_bmp_header_raw['blue_mask'] >> $blueshift)) * 255));
595  $thisfile_bmp['data'][$row][$col] = (($red << 16) | ($green << 8) | ($blue));
596  }
597  while (($pixeldataoffset % 4) != 0) {
598  // lines are padded to nearest DWORD
599  $pixeldataoffset++;
600  }
601  }
602  break;
603 
604  default:
605  $ThisFileInfo['error'][] = 'Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data';
606  break;
607  }
608  break;
609 
610 
611  default: // unhandled compression type
612  $ThisFileInfo['error'][] = 'Unknown/unhandled compression type value ('.$thisfile_bmp_header_raw['compression'].') - cannot decompress pixel data';
613  break;
614  }
615  }
616 
617  return true;
618  }
619 
620 
621  function PlotBMP(&$BMPinfo) {
622  $starttime = time();
623  if (!isset($BMPinfo['bmp']['data']) || !is_array($BMPinfo['bmp']['data'])) {
624  echo 'ERROR: no pixel data<BR>';
625  return false;
626  }
627  set_time_limit(intval(round($BMPinfo['resolution_x'] * $BMPinfo['resolution_y'] / 10000)));
628  if ($im = ImageCreateTrueColor($BMPinfo['resolution_x'], $BMPinfo['resolution_y'])) {
629  for ($row = 0; $row < $BMPinfo['resolution_y']; $row++) {
630  for ($col = 0; $col < $BMPinfo['resolution_x']; $col++) {
631  if (isset($BMPinfo['bmp']['data'][$row][$col])) {
632  $red = ($BMPinfo['bmp']['data'][$row][$col] & 0x00FF0000) >> 16;
633  $green = ($BMPinfo['bmp']['data'][$row][$col] & 0x0000FF00) >> 8;
634  $blue = ($BMPinfo['bmp']['data'][$row][$col] & 0x000000FF);
635  $pixelcolor = ImageColorAllocate($im, $red, $green, $blue);
636  ImageSetPixel($im, $col, $row, $pixelcolor);
637  } else {
638  //echo 'ERROR: no data for pixel '.$row.' x '.$col.'<BR>';
639  //return false;
640  }
641  }
642  }
643  if (headers_sent()) {
644  echo 'plotted '.($BMPinfo['resolution_x'] * $BMPinfo['resolution_y']).' pixels in '.(time() - $starttime).' seconds<BR>';
645  ImageDestroy($im);
646  exit;
647  } else {
648  header('Content-type: image/png');
649  ImagePNG($im);
650  ImageDestroy($im);
651  return true;
652  }
653  }
654  return false;
655  }
656 
657  function BMPcompressionWindowsLookup($compressionid) {
658  static $BMPcompressionWindowsLookup = array(
659  0 => 'BI_RGB',
660  1 => 'BI_RLE8',
661  2 => 'BI_RLE4',
662  3 => 'BI_BITFIELDS',
663  4 => 'BI_JPEG',
664  5 => 'BI_PNG'
665  );
666  return (isset($BMPcompressionWindowsLookup[$compressionid]) ? $BMPcompressionWindowsLookup[$compressionid] : 'invalid');
667  }
668 
669  function BMPcompressionOS2Lookup($compressionid) {
670  static $BMPcompressionOS2Lookup = array(
671  0 => 'BI_RGB',
672  1 => 'BI_RLE8',
673  2 => 'BI_RLE4',
674  3 => 'Huffman 1D',
675  4 => 'BI_RLE24',
676  );
677  return (isset($BMPcompressionOS2Lookup[$compressionid]) ? $BMPcompressionOS2Lookup[$compressionid] : 'invalid');
678  }
679 
680 }
681 
682 
683 ?>