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