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