floIcon.php 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843
  1. <?php
  2. /***************************************************************************
  3. * Original floIcon copyright (C) 2007 by Joshua Hatfield. *
  4. * *
  5. * In order to use any part of this floIcon Class, you must comply with *
  6. * the license in 'license.doc'. In particular, you may not remove this *
  7. * copyright notice. *
  8. * *
  9. * Much time and thought has gone into this software and you are *
  10. * benefitting. We hope that you share your changes too. What goes *
  11. * around, comes around. *
  12. ***************************************************************************
  13. Version 1.1.1:
  14. Date: 2009-03-16
  15. Changes:
  16. I was a little hasty on that last update. A couple new bugs from 1.1.0 have
  17. been fixed.
  18. Version 1.1.0:
  19. Date: 2009-03-16
  20. Changes:
  21. Added Vista support.
  22. Fixed a number of minor bugs. Many thanks to Dvir Berebi for pointing
  23. them out.
  24. Version 1.0.5:
  25. Date: 2009-03-15
  26. Changes:
  27. Fixed bug when operating on low bit count images (1 or 4) with odd dimensions.
  28. Version 1.0.4:
  29. Date: 2007-05-25
  30. Changes:
  31. Made function not break quite so bad when reading a PNG file on a Vista icon.
  32. Now, you shouldn't be loading Vista icons anyways, but since I'm trying to
  33. upgrade to Vista compatible and I need a comparison, I've got to.
  34. Version 1.0.3:
  35. Date: 2007-05-25
  36. Changes:
  37. Okay, this one was just stupid. When adding large image support, I messed up
  38. and when reading, it doubled the image size. Now, it's fixed.
  39. I took the opportunity to also add a dummy AND map for 32 images on those
  40. readers who might be looking for it (though it's not supposed to be used.)
  41. Version 1.0.2:
  42. Date: 2007-05-24
  43. Sorry about two versions so quickly back to back, but something needed to be
  44. done with speed...things were getting too slow. I'm sure you'll be okay.
  45. Changes:
  46. Told palette determination to stop at 257 colors or is 32 bit because the
  47. palette is not used at that point and gets really slow when it starts
  48. getting into the high numbers, for instance, in photographs or gradient
  49. truecolor images with lots of unique colors.
  50. After experimenting, it appears that Windows XP does in fact support 256x256
  51. images and larger by setting the entry value to 0 in the 1 byte that holds
  52. that value and storing the true dimentions in the image header later. Of
  53. course, it still doesn't use these images in normal operation. XP will
  54. resize these and use them if no other images are available.
  55. Wrapped main documentation (this) to the 80th column for easier reading.
  56. Version 1.0.1:
  57. Date: 2007-05-23
  58. Thank you everyone for actively using the implementation on my site and
  59. illuminating me very quickly to a number of glaring bugs in this class.
  60. Changes:
  61. Added version history.
  62. Fixed bug with non-standard sizes in AND map reading AND writing.
  63. Fixed bug with palette images using non-black color in backgrounds.
  64. Fixed bug with height/width reversal reading files.
  65. Version 1.0.0:
  66. Date: 2007-05-17
  67. Original release date.
  68. Foreword:
  69. If you are simply in the effort of making an ICO file, may I recommend visiting
  70. my site, http://www.flobi.com/ , and clicking on floIcon. I have a fully
  71. functional implementation (on which the sample.php is based) where you can also
  72. see recent icons submitted by other visitors. No registration required, no
  73. intrusive ads. (As of this writing, there aren't actually any ads at all, I
  74. might add google ads at some point.)
  75. If you are trying to get an idea of how ICO files, work, may I recommend the
  76. page I used, http://www.daubnet.com/formats/ICO.html . It does not fully cover
  77. icon files, but it does a very good job of what it does. Any additional
  78. information, I will try to post at
  79. http://www.flobi.com/test/floIcon/more_on_icons.php for your convenience.
  80. If you are trying to get an idea of how image resource files work, I recommend
  81. ANY other class that deals with images. This class essentially plots points on
  82. the image, and that's not perticularly advanced.
  83. For any purpose, I wish you luck and feel free to contact me with any bugs,
  84. comments, questions, etc. - Flobi
  85. Summary:
  86. This class parses ICO files. It reads directly from the ICO file, headers
  87. first, so that if you are only retrieving 1 image, the entire ICO file need not
  88. be parsed. It supports merging ICO files. It supports adding PHP image
  89. resources as new images to the file. It has an export capability that can
  90. easily be written to a new (or the same) ICO file for saving ICO files. All
  91. sizes from 1x1 to 255x255 pixels and 1, 4, 8, 24 (plus transparency) and 32 bit
  92. images are supported. Image retrieval by size is supported.
  93. Included is a fully functional sample that allows users to upload ICO, GIF,
  94. JPEG and PNG files into a session icon file as well as remove images from the
  95. file and download the completed file (sample.php).
  96. Known Limitations: Does not support Vista icons. Does not support inversion
  97. palette method (because of the limitations of the PHP image resource).
  98. Addendum on Limitations:
  99. Windows Vista has added support for 256x256 size icons and now stores files as
  100. PNG's. This class is for older ICO files. A new class is currently under
  101. development that supports the new Windows Vista format.
  102. Palette inversion (my name for this technique) is the technique of using a black
  103. pixel (0, 0, 0) with a 1 "AND" pixel. White pixels with a 1 "AND" show
  104. transparent (or "don't" show). Black pixels with a 1 "AND" show inverted
  105. (sometimes). Because this method isn't uniformly supported or documented and
  106. the PHP image resource doesn't support it, I have decided to not as well. This
  107. does not apply to 32 bit images which include alpha transparency and no AND map.
  108. Though other functions exist, these are the only ones I believe offer usefulness
  109. to be public.
  110. floIcon public functions:
  111. readICO($file, $offset = 0)
  112. Loads the icon from the specified filepath ($file) starting at the
  113. specified file offset ($offset). This function MERGES the loaded icon
  114. images into the floIcon object.
  115. formatICO($offset = 0)
  116. Returns the current floIcon object formatted as an .ICO file with the
  117. file offset altered by $offset. If there are too many or too large
  118. images, causing any images saved past the 4,294,967,296th byte, this
  119. will return false. (This is well outside PHP's default memory
  120. allocation.)
  121. addImage($imageResource, $desiredBitCount = 1, $pngIfWidthExceeds = 48)
  122. Adds an icon image to the icon file based on the passed image resource
  123. ($imageResource). It will automatically determine the bit count, but
  124. can be persuaded to increase that to $desiredBitCount if that value is
  125. greater than the determined bit count.
  126. NOTE: The image resource is saved by REFERRENCE. So, if you edit it
  127. then call getImage, the resource returned will be the same, editions and
  128. all. Destruction of the resource will cause a new resource to be
  129. created in getImage().
  130. getImage($offset)
  131. Returns the php image resource either assigned by addImage or created
  132. dynamically at calltime by the data loaded with readICO(). The offset
  133. specified here ($offset) is the array offset starting at 0 and ending
  134. at countImages().
  135. getBestImage($height = 32, $width = 32)
  136. Returns the php images resource of the highest quality image closest to
  137. the size specified. This could be useful when you only want to display
  138. the icon in an icon list with a single representative icon. A resized
  139. copy of the highest quality available image will be returned if there is
  140. no 32 or 24 bit icon present at the speficied dimentions.
  141. sortImagesBySize()
  142. Sorts the $this->images array by order of size, smallest to largest.
  143. This is the optimal sorting for icon files.
  144. countImages()
  145. Returns a count of how many images are present in the current floIcon
  146. object.
  147. floIcon public variables:
  148. $images
  149. Contains a numerically indexed array of floIconImage objects.
  150. $updated
  151. True if an image has been added since load or last formatICO, otherwise
  152. false.
  153. floIconImage public functions:
  154. getHeader()
  155. Returns an associative array containing the information from the ICO
  156. image header.
  157. getEntry()
  158. Returns an associative array containing the information from the ICO
  159. entry header.
  160. NOTE: If this icon image was created by php image resource, this may not
  161. have accurate information until saving from floIcon with the formatICO()
  162. function. Specifically, offset information will be inaccurate.
  163. getImageResource()
  164. Returns a php image resource. Same as floIcon's getImage() function.
  165. setImageResource($imageResource, $desiredBitCount = 1, $pngIfWidthExceeds = 48)
  166. Changes this icon image based on the passed image resource
  167. ($imageResource). It will automatically determine the bit count, but can
  168. be persuaded to increase that to $desiredBitCount if that value is
  169. greater than the determined bit count.
  170. NOTE: The image resource is saved by REFERRENCE. So, if you edit it
  171. then call getImageResource, the resource returned will be the same,
  172. editions and all. Destruction of the resource will cause a new resource
  173. to be created in getImageResource().
  174. dealocateResource()
  175. This destroys the image resource variable, freeing up memory. The image
  176. will automatically be recreated when getImageResource is executed.
  177. */
  178. class floIcon {
  179. /*
  180. * $images is an associative array of offset integer => floIconImage object
  181. */
  182. var $images; // Array of floIconImage objects.
  183. var $updated = false;
  184. function floIcon() {
  185. $this->images = array();
  186. }
  187. function countImages() {
  188. return count($this->images);
  189. }
  190. function getBestImage($height = 32, $width = 32) {
  191. $best = false;
  192. $bestEntry = array();
  193. $secondBest = false;
  194. $secondBestEntry = array();
  195. foreach ($this->images as $key => $image) {
  196. $entry = $image->getEntry();
  197. $header = $image->getHeader();
  198. if (!@$entry["BitCount"]) {
  199. $entry["BitCount"] = $header["BitCount"];
  200. }
  201. if ($entry["Height"] == $height && $entry["Width"] == $width && $entry["BitCount"] == 32) {
  202. return $image->getImageResource();
  203. } elseif ($entry["Height"] == $height && $entry["Width"] == $width && $entry["BitCount"] > min(4, @$bestEntry["BitCount"])) {
  204. $best = $image;
  205. $bestEntry = $entry;
  206. } elseif (
  207. !$secondBest or
  208. $entry["Height"] >= $secondBestEntry["Height"] &&
  209. $entry["Width"] >= $secondBestEntry["Width"] &&
  210. $secondBestEntry["BitCount"] >= $secondBestEntry["BitCount"] and
  211. (
  212. $entry["Height"] <= 64 && $entry["Height"] > $secondBestEntry["Height"] and
  213. $entry["Height"] > 64 && $entry["Height"] < $secondBestEntry["Height"]
  214. ) ||
  215. (
  216. $entry["Width"] <= 64 && $entry["Width"] > $secondBestEntry["Width"] and
  217. $entry["Width"] > 64 && $entry["Width"] < $secondBestEntry["Width"]
  218. ) ||
  219. $secondBestEntry["BitCount"] > $secondBestEntry["BitCount"]
  220. ) {
  221. $secondBest = $image;
  222. $secondBestEntry = $entry;
  223. }
  224. }
  225. if ($best) {
  226. return $best->getImageResource();
  227. } elseif ($secondBest) {
  228. if ($secondBestEntry["Width"] != $width || $secondBestEntry["Height"] != $height) {
  229. $imageResource = $secondBest->getImageResource();
  230. $newImageResource = imagecreatetruecolor($width, $height);
  231. imagesavealpha($newImageResource, true);
  232. imagealphablending($newImageResource, false);
  233. imagecopyresampled($newImageResource, $imageResource, 0, 0, 0, 0, $width, $height, $secondBestEntry["Width"], $secondBestEntry["Height"]);
  234. $this->addImage($newImageResource, 32);
  235. return $newImageResource;
  236. } else {
  237. return $secondBest->getImageResource();
  238. }
  239. }
  240. }
  241. /*
  242. * readICO merges the icon images from the file to the current list
  243. */
  244. function readICO($file, $offset = 0) {
  245. if (file_exists($file) && filesize($file) > 0 && $filePointer = fopen($file, "r")) {
  246. fseek($filePointer, $offset);
  247. $header = unpack("SReserved/SType/SCount", fread($filePointer, 6));
  248. for ($t = 0; $t < $header["Count"]; $t++) {
  249. $newImage = new floIconImage();
  250. $newImage->readImageFromICO($filePointer, 6 + ($t * 16));
  251. $this->images[] = $newImage;
  252. }
  253. fclose($filePointer);
  254. }
  255. }
  256. function sortImagesBySize() {
  257. usort($this->images, array("floIcon", "_cmpObj"));
  258. }
  259. function formatICO($offset = 0) {
  260. $this->updated = false;
  261. $output = "";
  262. $output .= pack("SSS", 0, 1, count($this->images));
  263. $output_images = "";
  264. foreach ($this->images as $image) {
  265. $newImageOffset = $offset + // Whatever offset we've been given.
  266. 6 // Header.
  267. + (count($this->images) * 16) // Entries.
  268. + strlen($output_images);
  269. if ($newImageOffset > pow(256, 4) /* 4 bytes available for position */ ) {
  270. return false;
  271. }
  272. $output .= $image->formatEntryForIco($newImageOffset); // The images already in there.
  273. $output_images .= $image->formatImageForIco();
  274. }
  275. return $output.$output_images;
  276. }
  277. function _cmpObj($a, $b) {
  278. $aSize = $a->getSize();
  279. $bSize = $b->getSize();
  280. if ($aSize == $bSize) {
  281. return 0;
  282. }
  283. return ($aSize > $bSize)?1:-1;
  284. }
  285. function addImage($imageResource, $desiredBitCount = 1, $pngIfWidthExceeds = 48) {
  286. $this->updated = true;
  287. $newImage = new floIconImage();
  288. $newImage->setImageResource($imageResource, $desiredBitCount, $pngIfWidthExceeds);
  289. $this->images[] = $newImage;
  290. }
  291. function getImage($offset) {
  292. if (isset($this->images[$offset])) {
  293. return $this->images[$offset]->getImageResource();
  294. } else {
  295. return false;
  296. }
  297. }
  298. /*
  299. * getSize computes the
  300. */
  301. function getSize() {
  302. // Compute headers.
  303. $computedSize = 6; // Always 6 bytes.
  304. // Add image entry headers
  305. $computedSize += count($this->images) * 16; // Entry headers are always 16 bytes.
  306. foreach ($this->images as $image) {
  307. $computedSize += $image->getSize() + $image->getHeaderSize(); // getSize does not include the header.
  308. }
  309. }
  310. }
  311. class floIconImage {
  312. var $_imageResource = null;
  313. var $_entry = "";
  314. var $_entryIconFormat = "";
  315. var $_header = "";
  316. var $_headerIconFormat = "";
  317. var $_imageIconFormat = ""; // Includes palette and mask.
  318. function formatEntryForIco($offset) {
  319. // Format the entry, this has to be done here because we need the offset to get the full information.
  320. $this->_entry["FileOffset"] = $offset;
  321. $this->_entryIconFormat = pack("CCCCSSLL",
  322. $this->_entry["Width"]>=256?0:$this->_entry["Width"],
  323. $this->_entry["Height"]>=256?0:$this->_entry["Height"],
  324. $this->_entry["ColorCount"],
  325. $this->_entry["Reserved"],
  326. $this->_entry["Planes"],
  327. $this->_entry["BitCount"],
  328. $this->_entry["SizeInBytes"],
  329. $this->_entry["FileOffset"]
  330. );
  331. return $this->_entryIconFormat;
  332. }
  333. function formatImageForIco() {
  334. // Format the entry, this has to be done here because we need the offset to get the full information.
  335. return ($this->_headerIconFormat.$this->_imageIconFormat);
  336. }
  337. // Will move $bitCount UP to $desiredBitCount if $bitCount is found to be less than it.
  338. function setImageResource($imageResource, $desiredBitCount = 1, $pngIfWidthExceeds = 48) {
  339. imagesavealpha($imageResource, true);
  340. imagealphablending($imageResource, false);
  341. $height = imagesy($imageResource);
  342. $width = imagesx($imageResource);
  343. // Parse resource to determine header and icon format
  344. // Find Palette information
  345. $is_32bit = false; // Start with an assumption and get proven wrong.
  346. $hasTransparency = 0;
  347. $blackColor = false;
  348. $bitCount = 0;
  349. $realPalette = array();
  350. $realIndexPalette = array();
  351. for ($x = 0; $x < $width && !$is_32bit; $x++) {
  352. for ($y = 0; $y < $height && !$is_32bit; $y++) {
  353. $colorIndex = imagecolorat($imageResource, $x, $y);
  354. $color = imagecolorsforindex($imageResource, $colorIndex);
  355. if ($color["alpha"] == 0) {
  356. // No point continuing if there's more than 256 colors or it's 32bit.
  357. if (count($realPalette) < 257 && !$is_32bit) {
  358. $inRealPalette = false;
  359. foreach($realPalette as $realPaletteKey => $realPaletteColor) {
  360. if (
  361. $color["red"] == $realPaletteColor["red"] and
  362. $color["green"] == $realPaletteColor["green"] and
  363. $color["blue"] == $realPaletteColor["blue"]
  364. ) {
  365. $inRealPalette = $realPaletteKey;
  366. break;
  367. }
  368. }
  369. if ($inRealPalette === false) {
  370. $realIndexPalette[$colorIndex] = count($realPalette);
  371. if (
  372. $blackColor === false and
  373. $color["red"] == 0 and
  374. $color["green"] == 0 and
  375. $color["blue"] == 0
  376. ) {
  377. $blackColor = count($realPalette);
  378. }
  379. $realPalette[] = $color;
  380. } else {
  381. $realIndexPalette[$colorIndex] = $inRealPalette;
  382. }
  383. }
  384. } else {
  385. $hasTransparency = 1;
  386. }
  387. if ($color["alpha"] != 0 && $color["alpha"] != 127) {
  388. $is_32bit = true;
  389. }
  390. }
  391. }
  392. if ($is_32bit) {
  393. $colorCount = 0;
  394. $bitCount = 32;
  395. } else {
  396. if ($hasTransparency && $blackColor === false) {
  397. // We need a black color to facilitate transparency. Unfortunately, this can
  398. // increase the palette size by 1 if there's no other black color.
  399. $blackColor = count($realPalette);
  400. $color = array(
  401. "red" => 0,
  402. "blue" => 0,
  403. "green" => 0,
  404. "alpha" => 0
  405. );
  406. $realPalette[] = $color;
  407. }
  408. $colorCount = count($realPalette);
  409. if ($colorCount > 256 || $colorCount == 0) {
  410. $bitCount = 24;
  411. } elseif ($colorCount > 16) {
  412. $bitCount = 8;
  413. // 8 bit
  414. } elseif ($colorCount > 2) {
  415. $bitCount = 4;
  416. // 4 bit
  417. } else {
  418. $bitCount = 1;
  419. // 1 bit
  420. }
  421. if ($desiredBitCount > $bitCount) {
  422. $bitCount = $desiredBitCount;
  423. }
  424. switch ($bitCount) {
  425. case 24:
  426. $colorCount = 0;
  427. break;
  428. case 8:
  429. $colorCount = 256;
  430. break;
  431. case 4:
  432. $colorCount = 16;
  433. break;
  434. case 1:
  435. $colorCount = 2;
  436. break;
  437. }
  438. }
  439. // Create $this->_imageIconFormat...
  440. $this->_imageIconFormat = "";
  441. if ($bitCount < 24) {
  442. $iconPalette = array();
  443. // Save Palette
  444. foreach ($realIndexPalette as $colorIndex => $paletteIndex) {
  445. $color = $realPalette[$paletteIndex];
  446. $this->_imageIconFormat .= pack("CCCC", $color["blue"], $color["green"], $color["red"], 0);
  447. }
  448. while (strlen($this->_imageIconFormat) < $colorCount * 4) {
  449. $this->_imageIconFormat .= pack("CCCC", 0, 0, 0, 0);
  450. }
  451. // Save Each Pixel as Palette Entry
  452. $byte = 0; // For $bitCount < 8 math
  453. $bitPosition = 0; // For $bitCount < 8 math
  454. for ($y = 0; $y < $height; $y++) {
  455. for ($x = 0; $x < $width; $x++) {
  456. $color = imagecolorat($imageResource, $x, $height-$y-1);
  457. if (isset($realIndexPalette[$color])) {
  458. $color = $realIndexPalette[$color];
  459. } else {
  460. $color = $blackColor;
  461. }
  462. if ($bitCount < 8) {
  463. $bitPosition += $bitCount;
  464. $colorAdjusted = $color * pow(2, 8 - $bitPosition);
  465. $byte += $colorAdjusted;
  466. if ($bitPosition == 8) {
  467. $this->_imageIconFormat .= chr($byte);
  468. $bitPosition = 0;
  469. $byte = 0;
  470. }
  471. } else {
  472. $this->_imageIconFormat .= chr($color);
  473. }
  474. }
  475. // Each row ends with dumping the remaining bits and filling up to the 32bit line with 0's.
  476. if ($bitPosition) {
  477. $this->_imageIconFormat .= chr($byte);
  478. $bitPosition = 0;
  479. $byte = 0;
  480. }
  481. if (strlen($this->_imageIconFormat)%4) $this->_imageIconFormat .= str_repeat(chr(0), 4-(strlen($this->_imageIconFormat)%4));
  482. }
  483. } else {
  484. // Save each pixel.
  485. for ($y = 0; $y < $height; $y++) {
  486. for ($x = 0; $x < $width; $x++) {
  487. $color = imagecolorat($imageResource, $x, $height-$y-1);
  488. $color = imagecolorsforindex($imageResource, $color);
  489. if ($bitCount == 24) {
  490. if ($color["alpha"]) {
  491. $this->_imageIconFormat .= pack("CCC", 0, 0, 0);
  492. } else {
  493. $this->_imageIconFormat .= pack("CCC", $color["blue"], $color["green"], $color["red"]);
  494. }
  495. } else {
  496. $color["alpha"] = round((127-$color["alpha"]) / 127 * 255);
  497. $this->_imageIconFormat .= pack("CCCC", $color["blue"], $color["green"], $color["red"], $color["alpha"]);
  498. }
  499. }
  500. if (strlen($this->_imageIconFormat)%4) $this->_imageIconFormat .= str_repeat(chr(0), 4-(strlen($this->_imageIconFormat)%4));
  501. }
  502. }
  503. // save AND map (transparency)
  504. $byte = 0; // For $bitCount < 8 math
  505. $bitPosition = 0; // For $bitCount < 8 math
  506. for ($y = 0; $y < $height; $y++) {
  507. for ($x = 0; $x < $width; $x++) {
  508. if ($bitCount < 32) {
  509. $color = imagecolorat($imageResource, $x, $height-$y-1);
  510. $color = imagecolorsforindex($imageResource, $color);
  511. $color = $color["alpha"] == 127?1:0;
  512. } else {
  513. $color = 0;
  514. }
  515. $bitPosition += 1;
  516. $colorAdjusted = $color * pow(2, 8 - $bitPosition);
  517. $byte += $colorAdjusted;
  518. if ($bitPosition == 8) {
  519. $this->_imageIconFormat .= chr($byte);
  520. $bitPosition = 0;
  521. $byte = 0;
  522. }
  523. }
  524. // Each row ends with dumping the remaining bits and filling up to the 32bit line with 0's.
  525. if ($bitPosition) {
  526. $this->_imageIconFormat .= chr($byte);
  527. $bitPosition = 0; // For $bitCount < 8 math
  528. $byte = 0;
  529. }
  530. while (strlen($this->_imageIconFormat)%4) {
  531. $this->_imageIconFormat .= chr(0);
  532. }
  533. }
  534. if ($colorCount >= 256) {
  535. $colorCount = 0;
  536. }
  537. // Create header information...
  538. $this->_header = array(
  539. "Size" => 40,
  540. "Width" => $width,
  541. "Height" => $height*2,
  542. "Planes" => 1,
  543. "BitCount" => $bitCount,
  544. "Compression" => 0,
  545. "ImageSize" => strlen($this->_imageIconFormat),
  546. "XpixelsPerM" => 0,
  547. "YpixelsPerM" => 0,
  548. "ColorsUsed" => $colorCount,
  549. "ColorsImportant" => 0,
  550. );
  551. $this->_headerIconFormat = pack("LLLSSLLLLLL",
  552. $this->_header["Size"],
  553. $this->_header["Width"],
  554. $this->_header["Height"],
  555. $this->_header["Planes"],
  556. $this->_header["BitCount"],
  557. $this->_header["Compression"],
  558. $this->_header["ImageSize"],
  559. $this->_header["XpixelsPerM"],
  560. $this->_header["YpixelsPerM"],
  561. $this->_header["ColorsUsed"],
  562. $this->_header["ColorsImportant"]
  563. );
  564. $this->_entry = array(
  565. "Width" => $width,
  566. "Height" => $height,
  567. "ColorCount" => $colorCount,
  568. "Reserved" => 0,
  569. "Planes" => 1,
  570. "BitCount" => $bitCount,
  571. "SizeInBytes" => $this->_header["Size"] + $this->_header["ImageSize"],
  572. "FileOffset" => -1,
  573. );
  574. $this->_entryIconFormat = ""; // This won't get set until it's needed with the offset.
  575. $this->_imageResource = $imageResource;
  576. // Make png if width exceeds limit for old ico style
  577. if ($width > $pngIfWidthExceeds) {
  578. // I wish there were a better way to get the info than this. If anyone needs a version that doesn't use OB, I can have one that creates a TMP file.
  579. ob_start();
  580. imagepng($imageResource);
  581. $imageAsPng = ob_get_contents();
  582. ob_end_clean();
  583. $this->_headerIconFormat = "";
  584. $this->_imageIconFormat = $imageAsPng;
  585. }
  586. }
  587. function _createImageResource() {
  588. if ($newImage = @imagecreatefromstring($this->_headerIconFormat.$this->_imageIconFormat)) {
  589. // Vista supports PNG.
  590. $this->_headerIconFormat = "";
  591. $this->_imageIconFormat = $this->_headerIconFormat.$this->_imageIconFormat;
  592. imagesavealpha($newImage, true);
  593. imagealphablending($newImage, false);
  594. $this->_imageResource = $newImage;
  595. } elseif ($this->_entry["Height"] <= 1024 && $this->_entry["Width"] <= 1024) {
  596. $newImage = imagecreatetruecolor($this->_entry["Width"], $this->_entry["Height"]);
  597. imagesavealpha($newImage, true);
  598. imagealphablending($newImage, false);
  599. $readPosition = 0;
  600. $palette = array();
  601. if ($this->_header["BitCount"] < 24) {
  602. // Read Palette for low bitcounts
  603. $colorsInPalette = $this->_header["ColorsUsed"]?$this->_header["ColorsUsed"]:$this->_entry["ColorCount"];
  604. for ($t = 0; $t < pow(2, $this->_header["BitCount"]); $t++) {
  605. $blue = ord($this->_imageIconFormat[$readPosition++]);
  606. $green = ord($this->_imageIconFormat[$readPosition++]);
  607. $red = ord($this->_imageIconFormat[$readPosition++]);
  608. $readPosition++; // Unused "Reserved" value.
  609. $existingPaletteEntry = imagecolorexactalpha($newImage, $red, $green, $blue, 0);
  610. if ($existingPaletteEntry >= 0) {
  611. $palette[] = $existingPaletteEntry;
  612. } else {
  613. $palette[] = imagecolorallocatealpha($newImage, $red, $green, $blue, 0);
  614. }
  615. }
  616. // XOR
  617. for ($y = 0; $y < $this->_entry["Height"]; $y++) {
  618. $colors = array();
  619. for ($x = 0; $x < $this->_entry["Width"]; $x++) {
  620. if ($this->_header["BitCount"] < 8) {
  621. $color = array_shift($colors);
  622. if (is_null($color)) {
  623. $byte = ord($this->_imageIconFormat[$readPosition++]);
  624. $tmp_color = 0;
  625. for ($t = 7; $t >= 0; $t--) {
  626. $bit_value = pow(2, $t);
  627. $bit = floor($byte / $bit_value);
  628. $byte = $byte - ($bit * $bit_value);
  629. $tmp_color += $bit * pow(2, $t%$this->_header["BitCount"]);
  630. if ($t%$this->_header["BitCount"] == 0) {
  631. array_push($colors, $tmp_color);
  632. $tmp_color = 0;
  633. }
  634. }
  635. $color = array_shift($colors);
  636. }
  637. } else {
  638. $color = ord($this->_imageIconFormat[$readPosition++]);
  639. }
  640. imagesetpixel($newImage, $x, $this->_entry["Height"]-$y-1, $palette[$color]) or die("can't set pixel");
  641. }
  642. // All rows end on the 32 bit
  643. if ($readPosition%4) $readPosition += 4-($readPosition%4);
  644. }
  645. } else {
  646. // BitCount >= 24, No Palette.
  647. // marking position because some icons mark all pixels transparent when using an AND map.
  648. $markPosition = $readPosition;
  649. $retry = true;
  650. $ignoreAlpha = false;
  651. while ($retry) {
  652. $alphas = array();
  653. $retry = false;
  654. for ($y = 0; $y < $this->_entry["Height"] and !$retry; $y++) {
  655. for ($x = 0; $x < $this->_entry["Width"] and !$retry; $x++) {
  656. $blue = ord($this->_imageIconFormat[$readPosition++]);
  657. $green = ord($this->_imageIconFormat[$readPosition++]);
  658. $red = ord($this->_imageIconFormat[$readPosition++]);
  659. if ($this->_header["BitCount"] < 32) {
  660. $alpha = 0;
  661. } elseif($ignoreAlpha) {
  662. $alpha = 0;
  663. $readPosition++;
  664. } else {
  665. $alpha = ord($this->_imageIconFormat[$readPosition++]);
  666. $alphas[$alpha] = $alpha;
  667. $alpha = 127-round($alpha/255*127);
  668. }
  669. $paletteEntry = imagecolorexactalpha($newImage, $red, $green, $blue, $alpha);
  670. if ($paletteEntry < 0) {
  671. $paletteEntry = imagecolorallocatealpha($newImage, $red, $green, $blue, $alpha);
  672. }
  673. imagesetpixel($newImage, $x, $this->_entry["Height"]-$y-1, $paletteEntry) or die("can't set pixel");
  674. }
  675. if ($readPosition%4) $readPosition += 4-($readPosition%4);
  676. }
  677. if ($this->_header["BitCount"] == 32 && isset($alphas[0]) && count($alphas) == 1) {
  678. $retry = true;
  679. $readPosition = $markPosition;
  680. $ignoreAlpha = true;
  681. }
  682. }
  683. }
  684. // AND map
  685. if ($this->_header["BitCount"] < 32 || $ignoreAlpha) {
  686. // Bitcount == 32, No AND (if using alpha).
  687. $palette[-1] = imagecolorallocatealpha($newImage, 0, 0, 0, 127);
  688. imagecolortransparent($newImage, $palette[-1]);
  689. for ($y = 0; $y < $this->_entry["Height"]; $y++) {
  690. $colors = array();
  691. for ($x = 0; $x < $this->_entry["Width"]; $x++) {
  692. $color = array_shift($colors);
  693. if (is_null($color)) {
  694. $byte = ord($this->_imageIconFormat[$readPosition++]);
  695. $tmp_color = 0;
  696. for ($t = 7; $t >= 0; $t--) {
  697. $bit_value = pow(2, $t);
  698. $bit = floor($byte / $bit_value);
  699. $byte = $byte - ($bit * $bit_value);
  700. array_push($colors, $bit);
  701. }
  702. $color = array_shift($colors);
  703. }
  704. if ($color) {
  705. imagesetpixel($newImage, $x, $this->_entry["Height"]-$y-1, $palette[-1]) or die("can't set pixel");
  706. }
  707. }
  708. // All rows end on the 32 bit.
  709. if ($readPosition%4) $readPosition += 4-($readPosition%4);
  710. }
  711. }
  712. if ($this->_header["BitCount"] < 24) {
  713. imagetruecolortopalette($newImage, true, pow(2, $this->_header["BitCount"]));
  714. }
  715. }
  716. $this->_imageResource = $newImage;
  717. }
  718. // this function expects that $_entry, $_header and $_imageIconFormat have already been read, specifically from readImageFromICO.
  719. // Don't call this function except from there.
  720. function readImageFromICO($filePointer, $entryOffset) {
  721. $tmpPosition = ftell($filePointer); // So any other applications won't loose their position.
  722. // Get the entry.
  723. fseek($filePointer, $entryOffset);
  724. $this->_entryIconFormat = fread($filePointer, 16);
  725. $this->_entry = unpack("CWidth/CHeight/CColorCount/CReserved/SPlanes/SBitCount/LSizeInBytes/LFileOffset", $this->_entryIconFormat);
  726. // Position the file pointer.
  727. fseek($filePointer, $this->_entry["FileOffset"]);
  728. // Get the header.
  729. $this->_headerIconFormat = fread($filePointer, 40);
  730. $this->_header = unpack("LSize/LWidth/LHeight/SPlanes/SBitCount/LCompression/LImageSize/LXpixelsPerM/LYpixelsPerM/LColorsUsed/LColorsImportant", $this->_headerIconFormat);
  731. // Get the image.
  732. $this->_imageIconFormat = @fread($filePointer, $this->_entry["SizeInBytes"] - strlen($this->_headerIconFormat));
  733. fseek($filePointer, $tmpPosition); // So any other applications won't loose their position.
  734. if ($newImage = @imagecreatefromstring($this->_headerIconFormat.$this->_imageIconFormat)) {
  735. // This is a PNG, the supposed header information is useless.
  736. $this->_header = array (
  737. "Size" => 0,
  738. "Width" => imagesx($newImage),
  739. "Height" => imagesy($newImage) * 2,
  740. "Planes" => 0,
  741. "BitCount" => 32,
  742. "Compression" => 0,
  743. "ImageSize" => strlen($this->_imageIconFormat),
  744. "XpixelsPerM" => 0,
  745. "YpixelsPerM" => 0,
  746. "ColorsUsed" => 0,
  747. "ColorsImportant" => 0,
  748. );
  749. imagedestroy($newImage);
  750. }
  751. // Support for larger images requires entry marked as 0.
  752. if ($this->_entry["Width"] == 0) {
  753. $this->_entry["Width"] = $this->_header["Width"];
  754. }
  755. if ($this->_entry["Height"] == 0) {
  756. $this->_entry["Height"] = $this->_header["Height"]/2;
  757. }
  758. }
  759. function getHeader() {
  760. return $this->_header;
  761. }
  762. function getEntry() {
  763. return $this->_entry;
  764. }
  765. function floIconImage() {
  766. }
  767. function getHeaderSize() {
  768. return strlen($this->_headerIconFormat);
  769. }
  770. function getSize() {
  771. return strlen($this->_imageIconFormat);
  772. }
  773. function getImageResource() {
  774. if (!$this->_imageResource) $this->_createImageResource();
  775. return $this->_imageResource;
  776. }
  777. function dealocateResource() {
  778. @imagedestroy($this->_imageResource);
  779. $this->_imageResource = null;
  780. }
  781. }
  782. ?>