]> git.wh0rd.org - tt-rss.git/blame - lib/floIcon.php
floIcon: add experimental SizeInBytes cap
[tt-rss.git] / lib / floIcon.php
CommitLineData
4fe93274
AD
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
14Version 1.1.1:
15Date: 2009-03-16
16
17Changes:
18I was a little hasty on that last update. A couple new bugs from 1.1.0 have
6a1f4249 19been fixed.
4fe93274
AD
20
21Version 1.1.0:
22Date: 2009-03-16
23
24Changes:
6a1f4249 25Added Vista support.
4fe93274
AD
26Fixed a number of minor bugs. Many thanks to Dvir Berebi for pointing
27 them out.
28
29Version 1.0.5:
30Date: 2009-03-15
31
32Changes:
33Fixed bug when operating on low bit count images (1 or 4) with odd dimensions.
34
35Version 1.0.4:
36Date: 2007-05-25
37
38Changes:
39Made 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
43Version 1.0.3:
44Date: 2007-05-25
45
46Changes:
47Okay, 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.
49I 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
52Version 1.0.2:
53Date: 2007-05-24
54
55Sorry about two versions so quickly back to back, but something needed to be
56done with speed...things were getting too slow. I'm sure you'll be okay.
57
58Changes:
59Told 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.
63After 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.
68Wrapped main documentation (this) to the 80th column for easier reading.
69
70Version 1.0.1:
71Date: 2007-05-23
72
73Thank you everyone for actively using the implementation on my site and
74illuminating me very quickly to a number of glaring bugs in this class.
75
76Changes:
77Added version history.
78Fixed bug with non-standard sizes in AND map reading AND writing.
79Fixed bug with palette images using non-black color in backgrounds.
80Fixed bug with height/width reversal reading files.
81
82
83Version 1.0.0:
84Date: 2007-05-17
85Original release date.
86
87
88Foreword:
89If you are simply in the effort of making an ICO file, may I recommend visiting
90my site, http://www.flobi.com/ , and clicking on floIcon. I have a fully
91functional implementation (on which the sample.php is based) where you can also
92see recent icons submitted by other visitors. No registration required, no
93intrusive ads. (As of this writing, there aren't actually any ads at all, I
94might add google ads at some point.)
95
96If you are trying to get an idea of how ICO files, work, may I recommend the
97page I used, http://www.daubnet.com/formats/ICO.html . It does not fully cover
98icon files, but it does a very good job of what it does. Any additional
99information, I will try to post at
100http://www.flobi.com/test/floIcon/more_on_icons.php for your convenience.
101
102If you are trying to get an idea of how image resource files work, I recommend
103ANY other class that deals with images. This class essentially plots points on
104the image, and that's not perticularly advanced.
105
106For any purpose, I wish you luck and feel free to contact me with any bugs,
107comments, questions, etc. - Flobi
108
109Summary:
110This class parses ICO files. It reads directly from the ICO file, headers
111first, so that if you are only retrieving 1 image, the entire ICO file need not
112be parsed. It supports merging ICO files. It supports adding PHP image
113resources as new images to the file. It has an export capability that can
114easily be written to a new (or the same) ICO file for saving ICO files. All
115sizes from 1x1 to 255x255 pixels and 1, 4, 8, 24 (plus transparency) and 32 bit
116images are supported. Image retrieval by size is supported.
117
118Included is a fully functional sample that allows users to upload ICO, GIF,
119JPEG and PNG files into a session icon file as well as remove images from the
120file and download the completed file (sample.php).
121
122Known Limitations: Does not support Vista icons. Does not support inversion
123palette method (because of the limitations of the PHP image resource).
124
125Addendum on Limitations:
126Windows Vista has added support for 256x256 size icons and now stores files as
127PNG's. This class is for older ICO files. A new class is currently under
128development that supports the new Windows Vista format.
129
130Palette inversion (my name for this technique) is the technique of using a black
131pixel (0, 0, 0) with a 1 "AND" pixel. White pixels with a 1 "AND" show
132transparent (or "don't" show). Black pixels with a 1 "AND" show inverted
133(sometimes). Because this method isn't uniformly supported or documented and
134the PHP image resource doesn't support it, I have decided to not as well. This
135does not apply to 32 bit images which include alpha transparency and no AND map.
136
137Though other functions exist, these are the only ones I believe offer usefulness
138to be public.
139floIcon 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
184floIcon 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
191floIconImage 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*/
222class 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();
6a1f4249
AD
294 if ($newImage->readImageFromICO($filePointer, 6 + ($t * 16))) {
295 $this->images[] = $newImage;
296 }
4fe93274
AD
297 }
298 fclose($filePointer);
299 }
300 }
301 function sortImagesBySize() {
302 usort($this->images, array("floIcon", "_cmpObj"));
303 }
304 function formatICO($offset = 0) {
305 $this->updated = false;
306 $output = "";
307 $output .= pack("SSS", 0, 1, count($this->images));
308 $output_images = "";
309 foreach ($this->images as $image) {
310 $newImageOffset = $offset + // Whatever offset we've been given.
311 6 // Header.
312 + (count($this->images) * 16) // Entries.
313 + strlen($output_images);
314 if ($newImageOffset > pow(256, 4) /* 4 bytes available for position */ ) {
315 return false;
316 }
317 $output .= $image->formatEntryForIco($newImageOffset); // The images already in there.
318 $output_images .= $image->formatImageForIco();
319 }
320 return $output.$output_images;
321 }
322 function _cmpObj($a, $b) {
323 $aSize = $a->getSize();
324 $bSize = $b->getSize();
325 if ($aSize == $bSize) {
326 return 0;
327 }
328 return ($aSize > $bSize)?1:-1;
329 }
330
331 function addImage($imageResource, $desiredBitCount = 1, $pngIfWidthExceeds = 48) {
332 $this->updated = true;
333 $newImage = new floIconImage();
334 $newImage->setImageResource($imageResource, $desiredBitCount, $pngIfWidthExceeds);
335 $this->images[] = $newImage;
336 }
337 function getImage($offset) {
338 if (isset($this->images[$offset])) {
339 return $this->images[$offset]->getImageResource();
340 } else {
341 return false;
342 }
343 }
344 /*
345 * getSize computes the
346 */
347 function getSize() {
348 // Compute headers.
349 $computedSize = 6; // Always 6 bytes.
350 // Add image entry headers
351 $computedSize += count($this->images) * 16; // Entry headers are always 16 bytes.
352 foreach ($this->images as $image) {
353 $computedSize += $image->getSize() + $image->getHeaderSize(); // getSize does not include the header.
354 }
355 }
356}
357class floIconImage {
358 var $_imageResource = null;
359 var $_entry = "";
360 var $_entryIconFormat = "";
361 var $_header = "";
362 var $_headerIconFormat = "";
363 var $_imageIconFormat = ""; // Includes palette and mask.
364 function formatEntryForIco($offset) {
365 // Format the entry, this has to be done here because we need the offset to get the full information.
366 $this->_entry["FileOffset"] = $offset;
367 $this->_entryIconFormat = pack("CCCCSSLL",
368 $this->_entry["Width"]>=256?0:$this->_entry["Width"],
369 $this->_entry["Height"]>=256?0:$this->_entry["Height"],
370 $this->_entry["ColorCount"],
371 $this->_entry["Reserved"],
372 $this->_entry["Planes"],
373 $this->_entry["BitCount"],
374 $this->_entry["SizeInBytes"],
375 $this->_entry["FileOffset"]
376 );
377 return $this->_entryIconFormat;
378 }
379 function formatImageForIco() {
380 // Format the entry, this has to be done here because we need the offset to get the full information.
381 return ($this->_headerIconFormat.$this->_imageIconFormat);
382 }
383
384 // Will move $bitCount UP to $desiredBitCount if $bitCount is found to be less than it.
385 function setImageResource($imageResource, $desiredBitCount = 1, $pngIfWidthExceeds = 48) {
386 imagesavealpha($imageResource, true);
387 imagealphablending($imageResource, false);
388 $height = imagesy($imageResource);
389 $width = imagesx($imageResource);
6a1f4249 390
4fe93274
AD
391 // Parse resource to determine header and icon format
392
393 // Find Palette information
394 $is_32bit = false; // Start with an assumption and get proven wrong.
395 $hasTransparency = 0;
396 $blackColor = false;
397 $bitCount = 0;
398 $realPalette = array();
399 $realIndexPalette = array();
400 for ($x = 0; $x < $width && !$is_32bit; $x++) {
401 for ($y = 0; $y < $height && !$is_32bit; $y++) {
402 $colorIndex = imagecolorat($imageResource, $x, $y);
403 $color = imagecolorsforindex($imageResource, $colorIndex);
404 if ($color["alpha"] == 0) {
405 // No point continuing if there's more than 256 colors or it's 32bit.
406 if (count($realPalette) < 257 && !$is_32bit) {
407 $inRealPalette = false;
408 foreach($realPalette as $realPaletteKey => $realPaletteColor) {
409 if (
410 $color["red"] == $realPaletteColor["red"] and
411 $color["green"] == $realPaletteColor["green"] and
412 $color["blue"] == $realPaletteColor["blue"]
413 ) {
414 $inRealPalette = $realPaletteKey;
415 break;
416 }
417 }
418 if ($inRealPalette === false) {
419 $realIndexPalette[$colorIndex] = count($realPalette);
420 if (
421 $blackColor === false and
422 $color["red"] == 0 and
423 $color["green"] == 0 and
424 $color["blue"] == 0
425 ) {
426 $blackColor = count($realPalette);
427 }
428 $realPalette[] = $color;
429 } else {
430 $realIndexPalette[$colorIndex] = $inRealPalette;
431 }
432 }
433 } else {
434 $hasTransparency = 1;
435 }
436 if ($color["alpha"] != 0 && $color["alpha"] != 127) {
437 $is_32bit = true;
438 }
439 }
440 }
441 if ($is_32bit) {
442 $colorCount = 0;
443 $bitCount = 32;
444 } else {
445 if ($hasTransparency && $blackColor === false) {
446 // We need a black color to facilitate transparency. Unfortunately, this can
447 // increase the palette size by 1 if there's no other black color.
448 $blackColor = count($realPalette);
449 $color = array(
450 "red" => 0,
451 "blue" => 0,
452 "green" => 0,
453 "alpha" => 0
454 );
455 $realPalette[] = $color;
456 }
457 $colorCount = count($realPalette);
458 if ($colorCount > 256 || $colorCount == 0) {
459 $bitCount = 24;
460 } elseif ($colorCount > 16) {
461 $bitCount = 8;
462 // 8 bit
463 } elseif ($colorCount > 2) {
464 $bitCount = 4;
465 // 4 bit
466 } else {
467 $bitCount = 1;
468 // 1 bit
469 }
470 if ($desiredBitCount > $bitCount) {
471 $bitCount = $desiredBitCount;
472 }
473 switch ($bitCount) {
474 case 24:
475 $colorCount = 0;
476 break;
477 case 8:
478 $colorCount = 256;
479 break;
480 case 4:
481 $colorCount = 16;
482 break;
483 case 1:
484 $colorCount = 2;
485 break;
486 }
487 }
488 // Create $this->_imageIconFormat...
489 $this->_imageIconFormat = "";
490 if ($bitCount < 24) {
491 $iconPalette = array();
492 // Save Palette
493 foreach ($realIndexPalette as $colorIndex => $paletteIndex) {
494 $color = $realPalette[$paletteIndex];
495 $this->_imageIconFormat .= pack("CCCC", $color["blue"], $color["green"], $color["red"], 0);
496 }
497 while (strlen($this->_imageIconFormat) < $colorCount * 4) {
498 $this->_imageIconFormat .= pack("CCCC", 0, 0, 0, 0);
499 }
500 // Save Each Pixel as Palette Entry
501 $byte = 0; // For $bitCount < 8 math
502 $bitPosition = 0; // For $bitCount < 8 math
503 for ($y = 0; $y < $height; $y++) {
504 for ($x = 0; $x < $width; $x++) {
505 $color = imagecolorat($imageResource, $x, $height-$y-1);
506 if (isset($realIndexPalette[$color])) {
507 $color = $realIndexPalette[$color];
508 } else {
509 $color = $blackColor;
510 }
511
512 if ($bitCount < 8) {
513 $bitPosition += $bitCount;
514 $colorAdjusted = $color * pow(2, 8 - $bitPosition);
515 $byte += $colorAdjusted;
516 if ($bitPosition == 8) {
517 $this->_imageIconFormat .= chr($byte);
518 $bitPosition = 0;
519 $byte = 0;
520 }
521 } else {
522 $this->_imageIconFormat .= chr($color);
523 }
524 }
525 // Each row ends with dumping the remaining bits and filling up to the 32bit line with 0's.
526 if ($bitPosition) {
527 $this->_imageIconFormat .= chr($byte);
528 $bitPosition = 0;
529 $byte = 0;
530 }
531 if (strlen($this->_imageIconFormat)%4) $this->_imageIconFormat .= str_repeat(chr(0), 4-(strlen($this->_imageIconFormat)%4));
532 }
533 } else {
534 // Save each pixel.
535 for ($y = 0; $y < $height; $y++) {
536 for ($x = 0; $x < $width; $x++) {
537 $color = imagecolorat($imageResource, $x, $height-$y-1);
538 $color = imagecolorsforindex($imageResource, $color);
539 if ($bitCount == 24) {
540 if ($color["alpha"]) {
541 $this->_imageIconFormat .= pack("CCC", 0, 0, 0);
542 } else {
543 $this->_imageIconFormat .= pack("CCC", $color["blue"], $color["green"], $color["red"]);
544 }
545 } else {
546 $color["alpha"] = round((127-$color["alpha"]) / 127 * 255);
547 $this->_imageIconFormat .= pack("CCCC", $color["blue"], $color["green"], $color["red"], $color["alpha"]);
548 }
549 }
550 if (strlen($this->_imageIconFormat)%4) $this->_imageIconFormat .= str_repeat(chr(0), 4-(strlen($this->_imageIconFormat)%4));
551 }
552 }
553 // save AND map (transparency)
554 $byte = 0; // For $bitCount < 8 math
555 $bitPosition = 0; // For $bitCount < 8 math
556 for ($y = 0; $y < $height; $y++) {
557 for ($x = 0; $x < $width; $x++) {
558 if ($bitCount < 32) {
559 $color = imagecolorat($imageResource, $x, $height-$y-1);
560 $color = imagecolorsforindex($imageResource, $color);
561 $color = $color["alpha"] == 127?1:0;
562 } else {
563 $color = 0;
564 }
565
566 $bitPosition += 1;
567 $colorAdjusted = $color * pow(2, 8 - $bitPosition);
568 $byte += $colorAdjusted;
569 if ($bitPosition == 8) {
570 $this->_imageIconFormat .= chr($byte);
571 $bitPosition = 0;
572 $byte = 0;
573 }
574 }
575 // Each row ends with dumping the remaining bits and filling up to the 32bit line with 0's.
576 if ($bitPosition) {
577 $this->_imageIconFormat .= chr($byte);
578 $bitPosition = 0; // For $bitCount < 8 math
579 $byte = 0;
580 }
581 while (strlen($this->_imageIconFormat)%4) {
582 $this->_imageIconFormat .= chr(0);
583 }
584 }
585 if ($colorCount >= 256) {
586 $colorCount = 0;
587 }
588 // Create header information...
589 $this->_header = array(
590 "Size" => 40,
591 "Width" => $width,
592 "Height" => $height*2,
593 "Planes" => 1,
594 "BitCount" => $bitCount,
595 "Compression" => 0,
596 "ImageSize" => strlen($this->_imageIconFormat),
597 "XpixelsPerM" => 0,
598 "YpixelsPerM" => 0,
599 "ColorsUsed" => $colorCount,
600 "ColorsImportant" => 0,
601 );
602 $this->_headerIconFormat = pack("LLLSSLLLLLL",
603 $this->_header["Size"],
604 $this->_header["Width"],
605 $this->_header["Height"],
606
607 $this->_header["Planes"],
608 $this->_header["BitCount"],
609
610 $this->_header["Compression"],
611 $this->_header["ImageSize"],
612 $this->_header["XpixelsPerM"],
613 $this->_header["YpixelsPerM"],
614 $this->_header["ColorsUsed"],
615 $this->_header["ColorsImportant"]
616 );
617 $this->_entry = array(
618 "Width" => $width,
619 "Height" => $height,
620 "ColorCount" => $colorCount,
621 "Reserved" => 0,
622 "Planes" => 1,
623 "BitCount" => $bitCount,
624 "SizeInBytes" => $this->_header["Size"] + $this->_header["ImageSize"],
625 "FileOffset" => -1,
626 );
627 $this->_entryIconFormat = ""; // This won't get set until it's needed with the offset.
628 $this->_imageResource = $imageResource;
629
630 // Make png if width exceeds limit for old ico style
631 if ($width > $pngIfWidthExceeds) {
632 // 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.
633 ob_start();
634 imagepng($imageResource);
635 $imageAsPng = ob_get_contents();
636 ob_end_clean();
637 $this->_headerIconFormat = "";
638 $this->_imageIconFormat = $imageAsPng;
639 }
640
6a1f4249 641
4fe93274
AD
642 }
643 function _createImageResource() {
644 if ($newImage = @imagecreatefromstring($this->_headerIconFormat.$this->_imageIconFormat)) {
645 // Vista supports PNG.
646 $this->_headerIconFormat = "";
647 $this->_imageIconFormat = $this->_headerIconFormat.$this->_imageIconFormat;
648 imagesavealpha($newImage, true);
649 imagealphablending($newImage, false);
650 $this->_imageResource = $newImage;
651 } elseif ($this->_entry["Height"] <= 1024 && $this->_entry["Width"] <= 1024) {
652 $newImage = imagecreatetruecolor($this->_entry["Width"], $this->_entry["Height"]);
653 imagesavealpha($newImage, true);
654 imagealphablending($newImage, false);
655 $readPosition = 0;
656 $palette = array();
657 if ($this->_header["BitCount"] < 24) {
658 // Read Palette for low bitcounts
659 $colorsInPalette = $this->_header["ColorsUsed"]?$this->_header["ColorsUsed"]:$this->_entry["ColorCount"];
660 for ($t = 0; $t < pow(2, $this->_header["BitCount"]); $t++) {
661 $blue = ord($this->_imageIconFormat[$readPosition++]);
662 $green = ord($this->_imageIconFormat[$readPosition++]);
663 $red = ord($this->_imageIconFormat[$readPosition++]);
664 $readPosition++; // Unused "Reserved" value.
665 $existingPaletteEntry = imagecolorexactalpha($newImage, $red, $green, $blue, 0);
666 if ($existingPaletteEntry >= 0) {
667 $palette[] = $existingPaletteEntry;
668 } else {
669 $palette[] = imagecolorallocatealpha($newImage, $red, $green, $blue, 0);
670 }
671 }
672 // XOR
673 for ($y = 0; $y < $this->_entry["Height"]; $y++) {
674 $colors = array();
675 for ($x = 0; $x < $this->_entry["Width"]; $x++) {
676 if ($this->_header["BitCount"] < 8) {
677 $color = array_shift($colors);
678 if (is_null($color)) {
679 $byte = ord($this->_imageIconFormat[$readPosition++]);
680 $tmp_color = 0;
681 for ($t = 7; $t >= 0; $t--) {
682 $bit_value = pow(2, $t);
683 $bit = floor($byte / $bit_value);
684 $byte = $byte - ($bit * $bit_value);
685 $tmp_color += $bit * pow(2, $t%$this->_header["BitCount"]);
686 if ($t%$this->_header["BitCount"] == 0) {
687 array_push($colors, $tmp_color);
688 $tmp_color = 0;
689 }
690 }
691 $color = array_shift($colors);
692 }
693 } else {
694 $color = ord($this->_imageIconFormat[$readPosition++]);
695 }
696 imagesetpixel($newImage, $x, $this->_entry["Height"]-$y-1, $palette[$color]) or die("can't set pixel");
697 }
698 // All rows end on the 32 bit
699 if ($readPosition%4) $readPosition += 4-($readPosition%4);
700 }
701 } else {
702 // BitCount >= 24, No Palette.
703 // marking position because some icons mark all pixels transparent when using an AND map.
704 $markPosition = $readPosition;
705 $retry = true;
706 $ignoreAlpha = false;
707 while ($retry) {
708 $alphas = array();
709 $retry = false;
710 for ($y = 0; $y < $this->_entry["Height"] and !$retry; $y++) {
711 for ($x = 0; $x < $this->_entry["Width"] and !$retry; $x++) {
712 $blue = ord($this->_imageIconFormat[$readPosition++]);
713 $green = ord($this->_imageIconFormat[$readPosition++]);
714 $red = ord($this->_imageIconFormat[$readPosition++]);
715 if ($this->_header["BitCount"] < 32) {
716 $alpha = 0;
717 } elseif($ignoreAlpha) {
718 $alpha = 0;
719 $readPosition++;
720 } else {
721 $alpha = ord($this->_imageIconFormat[$readPosition++]);
722 $alphas[$alpha] = $alpha;
723 $alpha = 127-round($alpha/255*127);
724 }
725 $paletteEntry = imagecolorexactalpha($newImage, $red, $green, $blue, $alpha);
726 if ($paletteEntry < 0) {
727 $paletteEntry = imagecolorallocatealpha($newImage, $red, $green, $blue, $alpha);
728 }
729 imagesetpixel($newImage, $x, $this->_entry["Height"]-$y-1, $paletteEntry) or die("can't set pixel");
730 }
731 if ($readPosition%4) $readPosition += 4-($readPosition%4);
732 }
733 if ($this->_header["BitCount"] == 32 && isset($alphas[0]) && count($alphas) == 1) {
734 $retry = true;
735 $readPosition = $markPosition;
736 $ignoreAlpha = true;
737 }
738 }
739
740 }
741 // AND map
742 if ($this->_header["BitCount"] < 32 || $ignoreAlpha) {
743 // Bitcount == 32, No AND (if using alpha).
744 $palette[-1] = imagecolorallocatealpha($newImage, 0, 0, 0, 127);
745 imagecolortransparent($newImage, $palette[-1]);
746 for ($y = 0; $y < $this->_entry["Height"]; $y++) {
747 $colors = array();
748 for ($x = 0; $x < $this->_entry["Width"]; $x++) {
749 $color = array_shift($colors);
750 if (is_null($color)) {
751 $byte = ord($this->_imageIconFormat[$readPosition++]);
752 $tmp_color = 0;
753 for ($t = 7; $t >= 0; $t--) {
754 $bit_value = pow(2, $t);
755 $bit = floor($byte / $bit_value);
756 $byte = $byte - ($bit * $bit_value);
757 array_push($colors, $bit);
758 }
759 $color = array_shift($colors);
760 }
761 if ($color) {
762 imagesetpixel($newImage, $x, $this->_entry["Height"]-$y-1, $palette[-1]) or die("can't set pixel");
763 }
764 }
765 // All rows end on the 32 bit.
766 if ($readPosition%4) $readPosition += 4-($readPosition%4);
767 }
768 }
769 if ($this->_header["BitCount"] < 24) {
770 imagetruecolortopalette($newImage, true, pow(2, $this->_header["BitCount"]));
771 }
772 }
773 $this->_imageResource = $newImage;
774 }
775 // this function expects that $_entry, $_header and $_imageIconFormat have already been read, specifically from readImageFromICO.
776 // Don't call this function except from there.
777 function readImageFromICO($filePointer, $entryOffset) {
778 $tmpPosition = ftell($filePointer); // So any other applications won't loose their position.
779 // Get the entry.
780 fseek($filePointer, $entryOffset);
781 $this->_entryIconFormat = fread($filePointer, 16);
782 $this->_entry = unpack("CWidth/CHeight/CColorCount/CReserved/SPlanes/SBitCount/LSizeInBytes/LFileOffset", $this->_entryIconFormat);
783
6a1f4249
AD
784 if ($this->_entry["SizeInBytes"] > 16384)
785 return false;
786
4fe93274
AD
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;
6a1f4249
AD
822 }
823
824 return true;
4fe93274
AD
825 }
826 function getHeader() {
827 return $this->_header;
828 }
829 function getEntry() {
830 return $this->_entry;
831 }
832 function floIconImage() {
833 }
834 function getHeaderSize() {
835 return strlen($this->_headerIconFormat);
836 }
837 function getSize() {
838 return strlen($this->_imageIconFormat);
839 }
840 function getImageResource() {
841 if (!$this->_imageResource) $this->_createImageResource();
842 return $this->_imageResource;
843 }
844 function dealocateResource() {
845 @imagedestroy($this->_imageResource);
846 $this->_imageResource = null;
847 }
848}
6a1f4249 849?>