phpthumb.bmp.php 37 KB

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