XOOPS 2.5.6  Final
 All Classes Namespaces Files Functions Variables Pages
phpthumb.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 // Modified for use in phpThumb() - James Heinrich 2004.07.27 //
17 // //
19 
20 
21 class phpthumb_bmp {
22 
23  function phpthumb_bmp() {
24  return true;
25  }
26 
27  function phpthumb_bmp2gd(&$BMPdata, $truecolor=true) {
28  $ThisFileInfo = array();
29  if ($this->getid3_bmp($BMPdata, $ThisFileInfo, true, true)) {
30  $gd = $this->PlotPixelsGD($ThisFileInfo['bmp'], $truecolor);
31  return $gd;
32  }
33  return false;
34  }
35 
36  function phpthumb_bmpfile2gd($filename, $truecolor=true) {
37  if ($fp = @fopen($filename, 'rb')) {
38  $BMPdata = fread($fp, filesize($filename));
39  fclose($fp);
40  return $this->phpthumb_bmp2gd($BMPdata, $truecolor);
41  }
42  return false;
43  }
44 
45  function GD2BMPstring(&$gd_image) {
46  $imageX = ImageSX($gd_image);
47  $imageY = ImageSY($gd_image);
48 
49  $BMP = '';
50  for ($y = ($imageY - 1); $y >= 0; $y--) {
51  $thisline = '';
52  for ($x = 0; $x < $imageX; $x++) {
53  $argb = phpthumb_functions::GetPixelColor($gd_image, $x, $y);
54  $thisline .= chr($argb['blue']).chr($argb['green']).chr($argb['red']);
55  }
56  while (strlen($thisline) % 4) {
57  $thisline .= "\x00";
58  }
59  $BMP .= $thisline;
60  }
61 
62  $bmpSize = strlen($BMP) + 14 + 40;
63  // BITMAPFILEHEADER [14 bytes] - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_62uq.asp
64  $BITMAPFILEHEADER = 'BM'; // WORD bfType;
65  $BITMAPFILEHEADER .= phpthumb_functions::LittleEndian2String($bmpSize, 4); // DWORD bfSize;
66  $BITMAPFILEHEADER .= phpthumb_functions::LittleEndian2String( 0, 2); // WORD bfReserved1;
67  $BITMAPFILEHEADER .= phpthumb_functions::LittleEndian2String( 0, 2); // WORD bfReserved2;
68  $BITMAPFILEHEADER .= phpthumb_functions::LittleEndian2String( 54, 4); // DWORD bfOffBits;
69 
70  // BITMAPINFOHEADER - [40 bytes] http://msdn.microsoft.com/library/en-us/gdi/bitmaps_1rw2.asp
71  $BITMAPINFOHEADER = phpthumb_functions::LittleEndian2String( 40, 4); // DWORD biSize;
72  $BITMAPINFOHEADER .= phpthumb_functions::LittleEndian2String( $imageX, 4); // LONG biWidth;
73  $BITMAPINFOHEADER .= phpthumb_functions::LittleEndian2String( $imageY, 4); // LONG biHeight;
74  $BITMAPINFOHEADER .= phpthumb_functions::LittleEndian2String( 1, 2); // WORD biPlanes;
75  $BITMAPINFOHEADER .= phpthumb_functions::LittleEndian2String( 24, 2); // WORD biBitCount;
76  $BITMAPINFOHEADER .= phpthumb_functions::LittleEndian2String( 0, 4); // DWORD biCompression;
77  $BITMAPINFOHEADER .= phpthumb_functions::LittleEndian2String( 0, 4); // DWORD biSizeImage;
78  $BITMAPINFOHEADER .= phpthumb_functions::LittleEndian2String( 2835, 4); // LONG biXPelsPerMeter;
79  $BITMAPINFOHEADER .= phpthumb_functions::LittleEndian2String( 2835, 4); // LONG biYPelsPerMeter;
80  $BITMAPINFOHEADER .= phpthumb_functions::LittleEndian2String( 0, 4); // DWORD biClrUsed;
81  $BITMAPINFOHEADER .= phpthumb_functions::LittleEndian2String( 0, 4); // DWORD biClrImportant;
82 
83  return $BITMAPFILEHEADER.$BITMAPINFOHEADER.$BMP;
84  }
85 
86  function getid3_bmp(&$BMPdata, &$ThisFileInfo, $ExtractPalette=false, $ExtractData=false) {
87 
88  // shortcuts
89  $ThisFileInfo['bmp']['header']['raw'] = array();
90  $thisfile_bmp = &$ThisFileInfo['bmp'];
91  $thisfile_bmp_header = &$thisfile_bmp['header'];
92  $thisfile_bmp_header_raw = &$thisfile_bmp_header['raw'];
93 
94  // BITMAPFILEHEADER [14 bytes] - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_62uq.asp
95  // all versions
96  // WORD bfType;
97  // DWORD bfSize;
98  // WORD bfReserved1;
99  // WORD bfReserved2;
100  // DWORD bfOffBits;
101 
102  $offset = 0;
103  $overalloffset = 0;
104  $BMPheader = substr($BMPdata, $overalloffset, 14 + 40);
105  $overalloffset += (14 + 40);
106 
107  $thisfile_bmp_header_raw['identifier'] = substr($BMPheader, $offset, 2);
108  $offset += 2;
109 
110  if ($thisfile_bmp_header_raw['identifier'] != 'BM') {
111  $ThisFileInfo['error'][] = 'Expecting "BM" at offset '.intval(@$ThisFileInfo['avdataoffset']).', found "'.$thisfile_bmp_header_raw['identifier'].'"';
112  unset($ThisFileInfo['fileformat']);
113  unset($ThisFileInfo['bmp']);
114  return false;
115  }
116 
117  $thisfile_bmp_header_raw['filesize'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 4));
118  $offset += 4;
119  $thisfile_bmp_header_raw['reserved1'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 2));
120  $offset += 2;
121  $thisfile_bmp_header_raw['reserved2'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 2));
122  $offset += 2;
123  $thisfile_bmp_header_raw['data_offset'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 4));
124  $offset += 4;
125  $thisfile_bmp_header_raw['header_size'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 4));
126  $offset += 4;
127 
128 
129  // check if the hardcoded-to-1 "planes" is at offset 22 or 26
130  $planes22 = $this->LittleEndian2Int(substr($BMPheader, 22, 2));
131  $planes26 = $this->LittleEndian2Int(substr($BMPheader, 26, 2));
132  if (($planes22 == 1) && ($planes26 != 1)) {
133  $thisfile_bmp['type_os'] = 'OS/2';
134  $thisfile_bmp['type_version'] = 1;
135  } elseif (($planes26 == 1) && ($planes22 != 1)) {
136  $thisfile_bmp['type_os'] = 'Windows';
137  $thisfile_bmp['type_version'] = 1;
138  } elseif ($thisfile_bmp_header_raw['header_size'] == 12) {
139  $thisfile_bmp['type_os'] = 'OS/2';
140  $thisfile_bmp['type_version'] = 1;
141  } elseif ($thisfile_bmp_header_raw['header_size'] == 40) {
142  $thisfile_bmp['type_os'] = 'Windows';
143  $thisfile_bmp['type_version'] = 1;
144  } elseif ($thisfile_bmp_header_raw['header_size'] == 84) {
145  $thisfile_bmp['type_os'] = 'Windows';
146  $thisfile_bmp['type_version'] = 4;
147  } elseif ($thisfile_bmp_header_raw['header_size'] == 100) {
148  $thisfile_bmp['type_os'] = 'Windows';
149  $thisfile_bmp['type_version'] = 5;
150  } else {
151  $ThisFileInfo['error'][] = 'Unknown BMP subtype (or not a BMP file)';
152  unset($ThisFileInfo['fileformat']);
153  unset($ThisFileInfo['bmp']);
154  return false;
155  }
156 
157  $ThisFileInfo['fileformat'] = 'bmp';
158  $ThisFileInfo['video']['dataformat'] = 'bmp';
159  $ThisFileInfo['video']['lossless'] = true;
160  $ThisFileInfo['video']['pixel_aspect_ratio'] = (float) 1;
161 
162  if ($thisfile_bmp['type_os'] == 'OS/2') {
163 
164  // OS/2-format BMP
165  // http://netghost.narod.ru/gff/graphics/summary/os2bmp.htm
166 
167  // DWORD Size; /* Size of this structure in bytes */
168  // DWORD Width; /* Bitmap width in pixels */
169  // DWORD Height; /* Bitmap height in pixel */
170  // WORD NumPlanes; /* Number of bit planes (color depth) */
171  // WORD BitsPerPixel; /* Number of bits per pixel per plane */
172 
173  $thisfile_bmp_header_raw['width'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 2));
174  $offset += 2;
175  $thisfile_bmp_header_raw['height'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 2));
176  $offset += 2;
177  $thisfile_bmp_header_raw['planes'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 2));
178  $offset += 2;
179  $thisfile_bmp_header_raw['bits_per_pixel'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 2));
180  $offset += 2;
181 
182  $ThisFileInfo['video']['resolution_x'] = $thisfile_bmp_header_raw['width'];
183  $ThisFileInfo['video']['resolution_y'] = $thisfile_bmp_header_raw['height'];
184  $ThisFileInfo['video']['codec'] = 'BI_RGB '.$thisfile_bmp_header_raw['bits_per_pixel'].'-bit';
185  $ThisFileInfo['video']['bits_per_sample'] = $thisfile_bmp_header_raw['bits_per_pixel'];
186 
187  if ($thisfile_bmp['type_version'] >= 2) {
188  // DWORD Compression; /* Bitmap compression scheme */
189  // DWORD ImageDataSize; /* Size of bitmap data in bytes */
190  // DWORD XResolution; /* X resolution of display device */
191  // DWORD YResolution; /* Y resolution of display device */
192  // DWORD ColorsUsed; /* Number of color table indices used */
193  // DWORD ColorsImportant; /* Number of important color indices */
194  // WORD Units; /* Type of units used to measure resolution */
195  // WORD Reserved; /* Pad structure to 4-byte boundary */
196  // WORD Recording; /* Recording algorithm */
197  // WORD Rendering; /* Halftoning algorithm used */
198  // DWORD Size1; /* Reserved for halftoning algorithm use */
199  // DWORD Size2; /* Reserved for halftoning algorithm use */
200  // DWORD ColorEncoding; /* Color model used in bitmap */
201  // DWORD Identifier; /* Reserved for application use */
202 
203  $thisfile_bmp_header_raw['compression'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 4));
204  $offset += 4;
205  $thisfile_bmp_header_raw['bmp_data_size'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 4));
206  $offset += 4;
207  $thisfile_bmp_header_raw['resolution_h'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 4));
208  $offset += 4;
209  $thisfile_bmp_header_raw['resolution_v'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 4));
210  $offset += 4;
211  $thisfile_bmp_header_raw['colors_used'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 4));
212  $offset += 4;
213  $thisfile_bmp_header_raw['colors_important'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 4));
214  $offset += 4;
215  $thisfile_bmp_header_raw['resolution_units'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 2));
216  $offset += 2;
217  $thisfile_bmp_header_raw['reserved1'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 2));
218  $offset += 2;
219  $thisfile_bmp_header_raw['recording'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 2));
220  $offset += 2;
221  $thisfile_bmp_header_raw['rendering'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 2));
222  $offset += 2;
223  $thisfile_bmp_header_raw['size1'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 4));
224  $offset += 4;
225  $thisfile_bmp_header_raw['size2'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 4));
226  $offset += 4;
227  $thisfile_bmp_header_raw['color_encoding'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 4));
228  $offset += 4;
229  $thisfile_bmp_header_raw['identifier'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 4));
230  $offset += 4;
231 
232  $thisfile_bmp_header['compression'] = $this->BMPcompressionOS2Lookup($thisfile_bmp_header_raw['compression']);
233 
234  $ThisFileInfo['video']['codec'] = $thisfile_bmp_header['compression'].' '.$thisfile_bmp_header_raw['bits_per_pixel'].'-bit';
235  }
236 
237  } elseif ($thisfile_bmp['type_os'] == 'Windows') {
238 
239  // Windows-format BMP
240 
241  // BITMAPINFOHEADER - [40 bytes] http://msdn.microsoft.com/library/en-us/gdi/bitmaps_1rw2.asp
242  // all versions
243  // DWORD biSize;
244  // LONG biWidth;
245  // LONG biHeight;
246  // WORD biPlanes;
247  // WORD biBitCount;
248  // DWORD biCompression;
249  // DWORD biSizeImage;
250  // LONG biXPelsPerMeter;
251  // LONG biYPelsPerMeter;
252  // DWORD biClrUsed;
253  // DWORD biClrImportant;
254 
255  $thisfile_bmp_header_raw['width'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 4), true);
256  $offset += 4;
257  $thisfile_bmp_header_raw['height'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 4), true);
258  $offset += 4;
259  $thisfile_bmp_header_raw['planes'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 2));
260  $offset += 2;
261  $thisfile_bmp_header_raw['bits_per_pixel'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 2));
262  $offset += 2;
263  $thisfile_bmp_header_raw['compression'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 4));
264  $offset += 4;
265  $thisfile_bmp_header_raw['bmp_data_size'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 4));
266  $offset += 4;
267  $thisfile_bmp_header_raw['resolution_h'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 4), true);
268  $offset += 4;
269  $thisfile_bmp_header_raw['resolution_v'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 4), true);
270  $offset += 4;
271  $thisfile_bmp_header_raw['colors_used'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 4));
272  $offset += 4;
273  $thisfile_bmp_header_raw['colors_important'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 4));
274  $offset += 4;
275 
276  $thisfile_bmp_header['compression'] = $this->BMPcompressionWindowsLookup($thisfile_bmp_header_raw['compression']);
277  $ThisFileInfo['video']['resolution_x'] = $thisfile_bmp_header_raw['width'];
278  $ThisFileInfo['video']['resolution_y'] = $thisfile_bmp_header_raw['height'];
279  $ThisFileInfo['video']['codec'] = $thisfile_bmp_header['compression'].' '.$thisfile_bmp_header_raw['bits_per_pixel'].'-bit';
280  $ThisFileInfo['video']['bits_per_sample'] = $thisfile_bmp_header_raw['bits_per_pixel'];
281 
282  if (($thisfile_bmp['type_version'] >= 4) || ($thisfile_bmp_header_raw['compression'] == 3)) {
283  // should only be v4+, but BMPs with type_version==1 and BI_BITFIELDS compression have been seen
284  $BMPheader .= substr($BMPdata, $overalloffset, 44);
285  $overalloffset += 44;
286 
287  // BITMAPV4HEADER - [44 bytes] - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_2k1e.asp
288  // Win95+, WinNT4.0+
289  // DWORD bV4RedMask;
290  // DWORD bV4GreenMask;
291  // DWORD bV4BlueMask;
292  // DWORD bV4AlphaMask;
293  // DWORD bV4CSType;
294  // CIEXYZTRIPLE bV4Endpoints;
295  // DWORD bV4GammaRed;
296  // DWORD bV4GammaGreen;
297  // DWORD bV4GammaBlue;
298  $thisfile_bmp_header_raw['red_mask'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 4));
299  $offset += 4;
300  $thisfile_bmp_header_raw['green_mask'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 4));
301  $offset += 4;
302  $thisfile_bmp_header_raw['blue_mask'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 4));
303  $offset += 4;
304  $thisfile_bmp_header_raw['alpha_mask'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 4));
305  $offset += 4;
306  $thisfile_bmp_header_raw['cs_type'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 4));
307  $offset += 4;
308  $thisfile_bmp_header_raw['ciexyz_red'] = substr($BMPheader, $offset, 4);
309  $offset += 4;
310  $thisfile_bmp_header_raw['ciexyz_green'] = substr($BMPheader, $offset, 4);
311  $offset += 4;
312  $thisfile_bmp_header_raw['ciexyz_blue'] = substr($BMPheader, $offset, 4);
313  $offset += 4;
314  $thisfile_bmp_header_raw['gamma_red'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 4));
315  $offset += 4;
316  $thisfile_bmp_header_raw['gamma_green'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 4));
317  $offset += 4;
318  $thisfile_bmp_header_raw['gamma_blue'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 4));
319  $offset += 4;
320 
321  $thisfile_bmp_header['ciexyz_red'] = $this->FixedPoint2_30(strrev($thisfile_bmp_header_raw['ciexyz_red']));
322  $thisfile_bmp_header['ciexyz_green'] = $this->FixedPoint2_30(strrev($thisfile_bmp_header_raw['ciexyz_green']));
323  $thisfile_bmp_header['ciexyz_blue'] = $this->FixedPoint2_30(strrev($thisfile_bmp_header_raw['ciexyz_blue']));
324  }
325 
326  if ($thisfile_bmp['type_version'] >= 5) {
327  $BMPheader .= substr($BMPdata, $overalloffset, 16);
328  $overalloffset += 16;
329 
330  // BITMAPV5HEADER - [16 bytes] - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_7c36.asp
331  // Win98+, Win2000+
332  // DWORD bV5Intent;
333  // DWORD bV5ProfileData;
334  // DWORD bV5ProfileSize;
335  // DWORD bV5Reserved;
336  $thisfile_bmp_header_raw['intent'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 4));
337  $offset += 4;
338  $thisfile_bmp_header_raw['profile_data_offset'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 4));
339  $offset += 4;
340  $thisfile_bmp_header_raw['profile_data_size'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 4));
341  $offset += 4;
342  $thisfile_bmp_header_raw['reserved3'] = $this->LittleEndian2Int(substr($BMPheader, $offset, 4));
343  $offset += 4;
344  }
345 
346  } else {
347 
348  $ThisFileInfo['error'][] = 'Unknown BMP format in header.';
349  return false;
350 
351  }
352 
353  if ($ExtractPalette || $ExtractData) {
354  $PaletteEntries = 0;
355  if ($thisfile_bmp_header_raw['bits_per_pixel'] < 16) {
356  $PaletteEntries = pow(2, $thisfile_bmp_header_raw['bits_per_pixel']);
357  } elseif (isset($thisfile_bmp_header_raw['colors_used']) && ($thisfile_bmp_header_raw['colors_used'] > 0) && ($thisfile_bmp_header_raw['colors_used'] <= 256)) {
358  $PaletteEntries = $thisfile_bmp_header_raw['colors_used'];
359  }
360  if ($PaletteEntries > 0) {
361  $BMPpalette = substr($BMPdata, $overalloffset, 4 * $PaletteEntries);
362  $overalloffset += 4 * $PaletteEntries;
363 
364  $paletteoffset = 0;
365  for ($i = 0; $i < $PaletteEntries; $i++) {
366  // RGBQUAD - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_5f8y.asp
367  // BYTE rgbBlue;
368  // BYTE rgbGreen;
369  // BYTE rgbRed;
370  // BYTE rgbReserved;
371  $blue = $this->LittleEndian2Int(substr($BMPpalette, $paletteoffset++, 1));
372  $green = $this->LittleEndian2Int(substr($BMPpalette, $paletteoffset++, 1));
373  $red = $this->LittleEndian2Int(substr($BMPpalette, $paletteoffset++, 1));
374  if (($thisfile_bmp['type_os'] == 'OS/2') && ($thisfile_bmp['type_version'] == 1)) {
375  // no padding byte
376  } else {
377  $paletteoffset++; // padding byte
378  }
379  $thisfile_bmp['palette'][$i] = (($red << 16) | ($green << 8) | ($blue));
380  }
381  }
382  }
383 
384  if ($ExtractData) {
385  $RowByteLength = ceil(($thisfile_bmp_header_raw['width'] * ($thisfile_bmp_header_raw['bits_per_pixel'] / 8)) / 4) * 4; // round up to nearest DWORD boundry
386 
387  $BMPpixelData = substr($BMPdata, $thisfile_bmp_header_raw['data_offset'], $thisfile_bmp_header_raw['height'] * $RowByteLength);
388  $overalloffset = $thisfile_bmp_header_raw['data_offset'] + ($thisfile_bmp_header_raw['height'] * $RowByteLength);
389 
390  $pixeldataoffset = 0;
391  switch (@$thisfile_bmp_header_raw['compression']) {
392 
393  case 0: // BI_RGB
394  switch ($thisfile_bmp_header_raw['bits_per_pixel']) {
395  case 1:
396  for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
397  for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col = $col) {
398  $paletteindexbyte = ord($BMPpixelData{$pixeldataoffset++});
399  for ($i = 7; $i >= 0; $i--) {
400  $paletteindex = ($paletteindexbyte & (0x01 << $i)) >> $i;
401  $thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex];
402  $col++;
403  }
404  }
405  while (($pixeldataoffset % 4) != 0) {
406  // lines are padded to nearest DWORD
407  $pixeldataoffset++;
408  }
409  }
410  break;
411 
412  case 4:
413  for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
414  for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col = $col) {
415  $paletteindexbyte = ord($BMPpixelData{$pixeldataoffset++});
416  for ($i = 1; $i >= 0; $i--) {
417  $paletteindex = ($paletteindexbyte & (0x0F << (4 * $i))) >> (4 * $i);
418  $thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex];
419  $col++;
420  }
421  }
422  while (($pixeldataoffset % 4) != 0) {
423  // lines are padded to nearest DWORD
424  $pixeldataoffset++;
425  }
426  }
427  break;
428 
429  case 8:
430  for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
431  for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col++) {
432  $paletteindex = ord($BMPpixelData{$pixeldataoffset++});
433  $thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex];
434  }
435  while (($pixeldataoffset % 4) != 0) {
436  // lines are padded to nearest DWORD
437  $pixeldataoffset++;
438  }
439  }
440  break;
441 
442  case 24:
443  for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
444  for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col++) {
445  $thisfile_bmp['data'][$row][$col] = (ord($BMPpixelData{$pixeldataoffset+2}) << 16) | (ord($BMPpixelData{$pixeldataoffset+1}) << 8) | ord($BMPpixelData{$pixeldataoffset});
446  $pixeldataoffset += 3;
447  }
448  while (($pixeldataoffset % 4) != 0) {
449  // lines are padded to nearest DWORD
450  $pixeldataoffset++;
451  }
452  }
453  break;
454 
455  case 32:
456  for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
457  for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col++) {
458  $thisfile_bmp['data'][$row][$col] = (ord($BMPpixelData{$pixeldataoffset+3}) << 24) | (ord($BMPpixelData{$pixeldataoffset+2}) << 16) | (ord($BMPpixelData{$pixeldataoffset+1}) << 8) | ord($BMPpixelData{$pixeldataoffset});
459  $pixeldataoffset += 4;
460  }
461  while (($pixeldataoffset % 4) != 0) {
462  // lines are padded to nearest DWORD
463  $pixeldataoffset++;
464  }
465  }
466  break;
467 
468  case 16:
469  // ?
470  break;
471 
472  default:
473  $ThisFileInfo['error'][] = 'Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data';
474  break;
475  }
476  break;
477 
478 
479  case 1: // BI_RLE8 - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_6x0u.asp
480  switch ($thisfile_bmp_header_raw['bits_per_pixel']) {
481  case 8:
482  $pixelcounter = 0;
483  while ($pixeldataoffset < strlen($BMPpixelData)) {
484  $firstbyte = $this->LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
485  $secondbyte = $this->LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
486  if ($firstbyte == 0) {
487 
488  // escaped/absolute mode - the first byte of the pair can be set to zero to
489  // indicate an escape character that denotes the end of a line, the end of
490  // a bitmap, or a delta, depending on the value of the second byte.
491  switch ($secondbyte) {
492  case 0:
493  // end of line
494  // no need for special processing, just ignore
495  break;
496 
497  case 1:
498  // end of bitmap
499  $pixeldataoffset = strlen($BMPpixelData); // force to exit loop just in case
500  break;
501 
502  case 2:
503  // delta - The 2 bytes following the escape contain unsigned values
504  // indicating the horizontal and vertical offsets of the next pixel
505  // from the current position.
506  $colincrement = $this->LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
507  $rowincrement = $this->LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
508  $col = ($pixelcounter % $thisfile_bmp_header_raw['width']) + $colincrement;
509  $row = ($thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width'])) - $rowincrement;
510  $pixelcounter = ($row * $thisfile_bmp_header_raw['width']) + $col;
511  break;
512 
513  default:
514  // In absolute mode, the first byte is zero and the second byte is a
515  // value in the range 03H through FFH. The second byte represents the
516  // number of bytes that follow, each of which contains the color index
517  // of a single pixel. Each run must be aligned on a word boundary.
518  for ($i = 0; $i < $secondbyte; $i++) {
519  $paletteindex = $this->LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
520  $col = $pixelcounter % $thisfile_bmp_header_raw['width'];
521  $row = $thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width']);
522  $thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex];
523  $pixelcounter++;
524  }
525  while (($pixeldataoffset % 2) != 0) {
526  // Each run must be aligned on a word boundary.
527  $pixeldataoffset++;
528  }
529  break;
530  }
531 
532  } else {
533 
534  // encoded mode - the first byte specifies the number of consecutive pixels
535  // to be drawn using the color index contained in the second byte.
536  for ($i = 0; $i < $firstbyte; $i++) {
537  $col = $pixelcounter % $thisfile_bmp_header_raw['width'];
538  $row = $thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width']);
539  $thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$secondbyte];
540  $pixelcounter++;
541  }
542 
543  }
544  }
545  break;
546 
547  default:
548  $ThisFileInfo['error'][] = 'Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data';
549  break;
550  }
551  break;
552 
553 
554 
555  case 2: // BI_RLE4 - http://msdn.microsoft.com/library/en-us/gdi/bitmaps_6x0u.asp
556  switch ($thisfile_bmp_header_raw['bits_per_pixel']) {
557  case 4:
558  $pixelcounter = 0;
559  while ($pixeldataoffset < strlen($BMPpixelData)) {
560  $firstbyte = $this->LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
561  $secondbyte = $this->LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
562  if ($firstbyte == 0) {
563 
564  // escaped/absolute mode - the first byte of the pair can be set to zero to
565  // indicate an escape character that denotes the end of a line, the end of
566  // a bitmap, or a delta, depending on the value of the second byte.
567  switch ($secondbyte) {
568  case 0:
569  // end of line
570  // no need for special processing, just ignore
571  break;
572 
573  case 1:
574  // end of bitmap
575  $pixeldataoffset = strlen($BMPpixelData); // force to exit loop just in case
576  break;
577 
578  case 2:
579  // delta - The 2 bytes following the escape contain unsigned values
580  // indicating the horizontal and vertical offsets of the next pixel
581  // from the current position.
582  $colincrement = $this->LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
583  $rowincrement = $this->LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
584  $col = ($pixelcounter % $thisfile_bmp_header_raw['width']) + $colincrement;
585  $row = ($thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width'])) - $rowincrement;
586  $pixelcounter = ($row * $thisfile_bmp_header_raw['width']) + $col;
587  break;
588 
589  default:
590  // In absolute mode, the first byte is zero. The second byte contains the number
591  // of color indexes that follow. Subsequent bytes contain color indexes in their
592  // high- and low-order 4 bits, one color index for each pixel. In absolute mode,
593  // each run must be aligned on a word boundary.
594  unset($paletteindexes);
595  for ($i = 0; $i < ceil($secondbyte / 2); $i++) {
596  $paletteindexbyte = $this->LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset++, 1));
597  $paletteindexes[] = ($paletteindexbyte & 0xF0) >> 4;
598  $paletteindexes[] = ($paletteindexbyte & 0x0F);
599  }
600  while (($pixeldataoffset % 2) != 0) {
601  // Each run must be aligned on a word boundary.
602  $pixeldataoffset++;
603  }
604 
605  foreach ($paletteindexes as $dummy => $paletteindex) {
606  $col = $pixelcounter % $thisfile_bmp_header_raw['width'];
607  $row = $thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width']);
608  $thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindex];
609  $pixelcounter++;
610  }
611  break;
612  }
613 
614  } else {
615 
616  // encoded mode - the first byte of the pair contains the number of pixels to be
617  // drawn using the color indexes in the second byte. The second byte contains two
618  // color indexes, one in its high-order 4 bits and one in its low-order 4 bits.
619  // The first of the pixels is drawn using the color specified by the high-order
620  // 4 bits, the second is drawn using the color in the low-order 4 bits, the third
621  // is drawn using the color in the high-order 4 bits, and so on, until all the
622  // pixels specified by the first byte have been drawn.
623  $paletteindexes[0] = ($secondbyte & 0xF0) >> 4;
624  $paletteindexes[1] = ($secondbyte & 0x0F);
625  for ($i = 0; $i < $firstbyte; $i++) {
626  $col = $pixelcounter % $thisfile_bmp_header_raw['width'];
627  $row = $thisfile_bmp_header_raw['height'] - 1 - (($pixelcounter - $col) / $thisfile_bmp_header_raw['width']);
628  $thisfile_bmp['data'][$row][$col] = $thisfile_bmp['palette'][$paletteindexes[($i % 2)]];
629  $pixelcounter++;
630  }
631 
632  }
633  }
634  break;
635 
636  default:
637  $ThisFileInfo['error'][] = 'Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data';
638  break;
639  }
640  break;
641 
642 
643  case 3: // BI_BITFIELDS
644  switch ($thisfile_bmp_header_raw['bits_per_pixel']) {
645  case 16:
646  case 32:
647  $redshift = 0;
648  $greenshift = 0;
649  $blueshift = 0;
650  if (!$thisfile_bmp_header_raw['red_mask'] || !$thisfile_bmp_header_raw['green_mask'] || !$thisfile_bmp_header_raw['blue_mask']) {
651  $ThisFileInfo['error'][] = 'missing $thisfile_bmp_header_raw[(red|green|blue)_mask]';
652  return false;
653  }
654  while ((($thisfile_bmp_header_raw['red_mask'] >> $redshift) & 0x01) == 0) {
655  $redshift++;
656  }
657  while ((($thisfile_bmp_header_raw['green_mask'] >> $greenshift) & 0x01) == 0) {
658  $greenshift++;
659  }
660  while ((($thisfile_bmp_header_raw['blue_mask'] >> $blueshift) & 0x01) == 0) {
661  $blueshift++;
662  }
663  for ($row = ($thisfile_bmp_header_raw['height'] - 1); $row >= 0; $row--) {
664  for ($col = 0; $col < $thisfile_bmp_header_raw['width']; $col++) {
665  $pixelvalue = $this->LittleEndian2Int(substr($BMPpixelData, $pixeldataoffset, $thisfile_bmp_header_raw['bits_per_pixel'] / 8));
666  $pixeldataoffset += $thisfile_bmp_header_raw['bits_per_pixel'] / 8;
667 
668  $red = intval(round(((($pixelvalue & $thisfile_bmp_header_raw['red_mask']) >> $redshift) / ($thisfile_bmp_header_raw['red_mask'] >> $redshift)) * 255));
669  $green = intval(round(((($pixelvalue & $thisfile_bmp_header_raw['green_mask']) >> $greenshift) / ($thisfile_bmp_header_raw['green_mask'] >> $greenshift)) * 255));
670  $blue = intval(round(((($pixelvalue & $thisfile_bmp_header_raw['blue_mask']) >> $blueshift) / ($thisfile_bmp_header_raw['blue_mask'] >> $blueshift)) * 255));
671  $thisfile_bmp['data'][$row][$col] = (($red << 16) | ($green << 8) | ($blue));
672  }
673  while (($pixeldataoffset % 4) != 0) {
674  // lines are padded to nearest DWORD
675  $pixeldataoffset++;
676  }
677  }
678  break;
679 
680  default:
681  $ThisFileInfo['error'][] = 'Unknown bits-per-pixel value ('.$thisfile_bmp_header_raw['bits_per_pixel'].') - cannot read pixel data';
682  break;
683  }
684  break;
685 
686 
687  default: // unhandled compression type
688  $ThisFileInfo['error'][] = 'Unknown/unhandled compression type value ('.$thisfile_bmp_header_raw['compression'].') - cannot decompress pixel data';
689  break;
690  }
691  }
692 
693  return true;
694  }
695 
696  function IntColor2RGB($color) {
697  $red = ($color & 0x00FF0000) >> 16;
698  $green = ($color & 0x0000FF00) >> 8;
699  $blue = ($color & 0x000000FF);
700  return array($red, $green, $blue);
701  }
702 
703  function PlotPixelsGD(&$BMPdata, $truecolor=true) {
704  $imagewidth = $BMPdata['header']['raw']['width'];
705  $imageheight = $BMPdata['header']['raw']['height'];
706 
707  if ($truecolor) {
708 
709  $gd = @ImageCreateTrueColor($imagewidth, $imageheight);
710 
711  } else {
712 
713  $gd = @ImageCreate($imagewidth, $imageheight);
714  if (!empty($BMPdata['palette'])) {
715  // create GD palette from BMP palette
716  foreach ($BMPdata['palette'] as $dummy => $color) {
717  list($r, $g, $b) = $this->IntColor2RGB($color);
718  ImageColorAllocate($gd, $r, $g, $b);
719  }
720  } else {
721  // create 216-color websafe palette
722  for ($r = 0x00; $r <= 0xFF; $r += 0x33) {
723  for ($g = 0x00; $g <= 0xFF; $g += 0x33) {
724  for ($b = 0x00; $b <= 0xFF; $b += 0x33) {
725  ImageColorAllocate($gd, $r, $g, $b);
726  }
727  }
728  }
729  }
730 
731  }
732  if (!is_resource($gd)) {
733  return false;
734  }
735 
736  foreach ($BMPdata['data'] as $row => $colarray) {
737  if (!phpthumb_functions::FunctionIsDisabled('set_time_limit')) {
738  set_time_limit(30);
739  }
740  foreach ($colarray as $col => $color) {
741  list($red, $green, $blue) = $this->IntColor2RGB($color);
742  if ($truecolor) {
743  $pixelcolor = ImageColorAllocate($gd, $red, $green, $blue);
744  } else {
745  $pixelcolor = ImageColorClosest($gd, $red, $green, $blue);
746  }
747  ImageSetPixel($gd, $col, $row, $pixelcolor);
748  }
749  }
750  return $gd;
751  }
752 
753  function PlotBMP(&$BMPinfo) {
754  $starttime = time();
755  if (!isset($BMPinfo['bmp']['data']) || !is_array($BMPinfo['bmp']['data'])) {
756  echo 'ERROR: no pixel data<BR>';
757  return false;
758  }
759  if (!phpthumb_functions::FunctionIsDisabled('set_time_limit')) {
760  set_time_limit(intval(round($BMPinfo['resolution_x'] * $BMPinfo['resolution_y'] / 10000)));
761  }
762  $im = $this->PlotPixelsGD($BMPinfo['bmp']);
763  if (headers_sent()) {
764  echo 'plotted '.($BMPinfo['resolution_x'] * $BMPinfo['resolution_y']).' pixels in '.(time() - $starttime).' seconds<BR>';
765  ImageDestroy($im);
766  exit;
767  } else {
768  header('Content-Type: image/png');
769  ImagePNG($im);
770  ImageDestroy($im);
771  return true;
772  }
773  return false;
774  }
775 
776  function BMPcompressionWindowsLookup($compressionid) {
777  static $BMPcompressionWindowsLookup = array(
778  0 => 'BI_RGB',
779  1 => 'BI_RLE8',
780  2 => 'BI_RLE4',
781  3 => 'BI_BITFIELDS',
782  4 => 'BI_JPEG',
783  5 => 'BI_PNG'
784  );
785  return (isset($BMPcompressionWindowsLookup[$compressionid]) ? $BMPcompressionWindowsLookup[$compressionid] : 'invalid');
786  }
787 
788  function BMPcompressionOS2Lookup($compressionid) {
789  static $BMPcompressionOS2Lookup = array(
790  0 => 'BI_RGB',
791  1 => 'BI_RLE8',
792  2 => 'BI_RLE4',
793  3 => 'Huffman 1D',
794  4 => 'BI_RLE24',
795  );
796  return (isset($BMPcompressionOS2Lookup[$compressionid]) ? $BMPcompressionOS2Lookup[$compressionid] : 'invalid');
797  }
798 
799 
800  // from getid3.lib.php
801 
802  function trunc($floatnumber) {
803  // truncates a floating-point number at the decimal point
804  // returns int (if possible, otherwise float)
805  if ($floatnumber >= 1) {
806  $truncatednumber = floor($floatnumber);
807  } elseif ($floatnumber <= -1) {
808  $truncatednumber = ceil($floatnumber);
809  } else {
810  $truncatednumber = 0;
811  }
812  if ($truncatednumber <= 1073741824) { // 2^30
813  $truncatednumber = (int) $truncatednumber;
814  }
815  return $truncatednumber;
816  }
817 
818  function LittleEndian2Int($byteword) {
819  $intvalue = 0;
820  $byteword = strrev($byteword);
821  $bytewordlen = strlen($byteword);
822  for ($i = 0; $i < $bytewordlen; $i++) {
823  $intvalue += ord($byteword{$i}) * pow(256, ($bytewordlen - 1 - $i));
824  }
825  return $intvalue;
826  }
827 
828  function BigEndian2Int($byteword) {
829  return $this->LittleEndian2Int(strrev($byteword));
830  }
831 
832  function BigEndian2Bin($byteword) {
833  $binvalue = '';
834  $bytewordlen = strlen($byteword);
835  for ($i = 0; $i < $bytewordlen; $i++) {
836  $binvalue .= str_pad(decbin(ord($byteword{$i})), 8, '0', STR_PAD_LEFT);
837  }
838  return $binvalue;
839  }
840 
841  function FixedPoint2_30($rawdata) {
842  $binarystring = $this->BigEndian2Bin($rawdata);
843  return $this->Bin2Dec(substr($binarystring, 0, 2)) + (float) ($this->Bin2Dec(substr($binarystring, 2, 30)) / 1073741824);
844  }
845 
846  function Bin2Dec($binstring, $signed=false) {
847  $signmult = 1;
848  if ($signed) {
849  if ($binstring{0} == '1') {
850  $signmult = -1;
851  }
852  $binstring = substr($binstring, 1);
853  }
854  $decvalue = 0;
855  for ($i = 0; $i < strlen($binstring); $i++) {
856  $decvalue += ((int) substr($binstring, strlen($binstring) - $i - 1, 1)) * pow(2, $i);
857  }
858  return $this->CastAsInt($decvalue * $signmult);
859  }
860 
861  function CastAsInt($floatnum) {
862  // convert to float if not already
863  $floatnum = (float) $floatnum;
864 
865  // convert a float to type int, only if possible
866  if ($this->trunc($floatnum) == $floatnum) {
867  // it's not floating point
868  if ($floatnum <= 1073741824) { // 2^30
869  // it's within int range
870  $floatnum = (int) $floatnum;
871  }
872  }
873  return $floatnum;
874  }
875 
876 }
877 
878 ?>