PageRenderTime 300ms CodeModel.GetById 60ms app.highlight 89ms RepoModel.GetById 142ms app.codeStats 1ms

/getid3/module.graphic.bmp.php

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