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