]>
Commit | Line | Data |
---|---|---|
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 | // Position the file pointer. | |
784 | fseek($filePointer, $this->_entry["FileOffset"]); | |
785 | ||
786 | // Get the header. | |
787 | $this->_headerIconFormat = fread($filePointer, 40); | |
788 | $this->_header = unpack("LSize/LWidth/LHeight/SPlanes/SBitCount/LCompression/LImageSize/LXpixelsPerM/LYpixelsPerM/LColorsUsed/LColorsImportant", $this->_headerIconFormat); | |
789 | ||
790 | // Get the image. | |
791 | $this->_imageIconFormat = @fread($filePointer, $this->_entry["SizeInBytes"] - strlen($this->_headerIconFormat)); | |
792 | fseek($filePointer, $tmpPosition); // So any other applications won't loose their position. | |
793 | ||
794 | if ($newImage = @imagecreatefromstring($this->_headerIconFormat.$this->_imageIconFormat)) { | |
795 | // This is a PNG, the supposed header information is useless. | |
796 | $this->_header = array ( | |
797 | "Size" => 0, | |
798 | "Width" => imagesx($newImage), | |
799 | "Height" => imagesy($newImage) * 2, | |
800 | "Planes" => 0, | |
801 | "BitCount" => 32, | |
802 | "Compression" => 0, | |
803 | "ImageSize" => strlen($this->_imageIconFormat), | |
804 | "XpixelsPerM" => 0, | |
805 | "YpixelsPerM" => 0, | |
806 | "ColorsUsed" => 0, | |
807 | "ColorsImportant" => 0, | |
808 | ); | |
809 | imagedestroy($newImage); | |
810 | } | |
811 | ||
812 | // Support for larger images requires entry marked as 0. | |
813 | if ($this->_entry["Width"] == 0) { | |
814 | $this->_entry["Width"] = $this->_header["Width"]; | |
815 | } | |
816 | if ($this->_entry["Height"] == 0) { | |
817 | $this->_entry["Height"] = $this->_header["Height"]/2; | |
818 | } | |
819 | } | |
820 | function getHeader() { | |
821 | return $this->_header; | |
822 | } | |
823 | function getEntry() { | |
824 | return $this->_entry; | |
825 | } | |
826 | function floIconImage() { | |
827 | } | |
828 | function getHeaderSize() { | |
829 | return strlen($this->_headerIconFormat); | |
830 | } | |
831 | function getSize() { | |
832 | return strlen($this->_imageIconFormat); | |
833 | } | |
834 | function getImageResource() { | |
835 | if (!$this->_imageResource) $this->_createImageResource(); | |
836 | return $this->_imageResource; | |
837 | } | |
838 | function dealocateResource() { | |
839 | @imagedestroy($this->_imageResource); | |
840 | $this->_imageResource = null; | |
841 | } | |
842 | } | |
843 | ?> |