123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270 |
- <?php
- // Simple .ICO parsing. The ICO format is insanely complex and this may
- // fail to correctly handle some technically valid files, but it works
- // on the majority I've found.
- //
- // jimIcon was written in 2013 by Jim Paris <jim@jtan.com> and is
- // released under the terms of the CC0:
- //
- // To the extent possible under law, the author(s) have dedicated all
- // copyright and related and neighboring rights to this software to
- // the public domain worldwide. This software is distributed without
- // any arranty.
- //
- // You may have received a copy of the CC0 Public Domain Dedication
- // along with this software. If not, see
- // http://creativecommons.org/publicdomain/zero/1.0/
- class jimIcon {
- // Get an image color from a string
- function get_color($str, $img) {
- $b = ord($str[0]);
- $g = ord($str[1]);
- $r = ord($str[2]);
- if (strlen($str) > 3) {
- $a = 127 - (ord($str[3]) / 2);
- if ($a != 0 && $a != 127)
- $this->had_alpha = 1;
- } else {
- $a = 0;
- }
- if ($a != 127)
- $this->all_transaprent = 0;
- return imagecolorallocatealpha($img, $r, $g, $b, $a);
- }
- // Given a string with the contents of an .ICO,
- // return a GD image of the icon, or false on error.
- function fromiconstring($ico) {
- $this->error = "(unknown error)";
- $this->had_alpha = 0;
- // Read header
- if (strlen($ico) < 6) {
- $this->error = "too short";
- return false;
- }
- $h = unpack("vzero/vtype/vnum", $ico);
- // Must be ICO format with at least one image
- if ($h["zero"] != 0 || $h["type"] != 1 || $h["num"] == 0) {
- // See if we can just parse it with GD directly
- // if it's not ICO format; maybe it was a mislabeled
- // PNG or something.
- $i = @imagecreatefromstring($ico);
- if ($i) {
- imagesavealpha($i, true);
- return $i;
- }
- $this->error = "not ICO or other image";
- return false;
- }
- // Read directory entries to find the biggest image
- $most_pixels = 0;
- for ($i = 0; $i < $h["num"]; $i++) {
- $entry = substr($ico, 6 + 16 * $i, 16);
- if (!$entry || strlen($entry) < 16)
- continue;
- $e = unpack("Cwidth/" .
- "Cheight/" .
- "Ccolors/" .
- "Czero/" .
- "vplanes/" .
- "vbpp/" .
- "Vsize/" .
- "Voffset/",
- $entry);
- if ($e["width"] == 0)
- $e["width"] = 256;
- if ($e["height"] == 0)
- $e["height"] = 256;
- if ($e["zero"] != 0) {
- $this->error = "nonzero reserved field";
- return false;
- }
- $pixels = $e["width"] * $e["height"];
- if ($pixels > $most_pixels) {
- $most_pixels = $pixels;
- $most = $e;
- }
- }
- if ($most_pixels == 0) {
- $this->error = "no pixels";
- return false;
- }
- $e = $most;
- // Extract image data
- $data = substr($ico, $e["offset"], $e["size"]);
- if (!$data || strlen($data) != $e["size"]) {
- $this->error = "bad image data";
- return false;
- }
- // See if we can parse it (might be PNG format here)
- $i = @imagecreatefromstring($data);
- if ($i) {
- imagesavealpha($img, true);
- return $i;
- }
- // Must be a BMP. Parse it ourselves.
- $img = imagecreatetruecolor($e["width"], $e["height"]);
- imagesavealpha($img, true);
- $bg = imagecolorallocatealpha($img, 255, 0, 0, 127);
- imagefill($img, 0, 0, $bg);
- // Skip over the BITMAPCOREHEADER or BITMAPINFOHEADER;
- // we'll just assume the palette and pixel data follow
- // in the most obvious format as described by the icon
- // directory entry.
- $bitmapinfo = unpack("Vsize", $data);
- if ($bitmapinfo["size"] == 40) {
- $info = unpack("Vsize/" .
- "Vwidth/" .
- "Vheight/" .
- "vplanes/" .
- "vbpp/" .
- "Vcompress/" .
- "Vsize/" .
- "Vxres/" .
- "Vyres/" .
- "Vpalcolors/" .
- "Vimpcolors/", $data);
- if ($e["bpp"] == 0) {
- $e["bpp"] = $info["bpp"];
- }
- }
- $data = substr($data, $bitmapinfo["size"]);
- $height = $e["height"];
- $width = $e["width"];
- $bpp = $e["bpp"];
- // For indexed images, we only support 1, 4, or 8 BPP
- switch ($bpp) {
- case 1:
- case 4:
- case 8:
- $indexed = 1;
- break;
- case 24:
- case 32:
- $indexed = 0;
- break;
- default:
- $this->error = "bad BPP $bpp";
- return false;
- }
- $offset = 0;
- if ($indexed) {
- $palette = array();
- $this->all_transparent = 1;
- for ($i = 0; $i < (1 << $bpp); $i++) {
- $entry = substr($data, $i * 4, 4);
- $palette[$i] = $this->get_color($entry, $img);
- }
- $offset = $i * 4;
- // Hack for some icons: if everything was transparent,
- // discard alpha channel.
- if ($this->all_transparent) {
- for ($i = 0; $i < (1 << $bpp); $i++) {
- $palette[$i] &= 0xffffff;
- }
- }
- }
- // Assume image data follows in bottom-up order.
- // First the "XOR" image
- if ((strlen($data) - $offset) < ($bpp * $height * $width / 8)) {
- $this->error = "short data";
- return false;
- }
- $XOR = array();
- for ($y = $height - 1; $y >= 0; $y--) {
- $x = 0;
- while ($x < $width) {
- if (!$indexed) {
- $bytes = $bpp / 8;
- $entry = substr($data, $offset, $bytes);
- $pixel = $this->get_color($entry, $img);
- $XOR[$y][$x] = $pixel;
- $x++;
- $offset += $bytes;
- } elseif ($bpp == 1) {
- $p = ord($data[$offset]);
- for ($b = 0x80; $b > 0; $b >>= 1) {
- if ($p & $b) {
- $pixel = $palette[1];
- } else {
- $pixel = $palette[0];
- }
- $XOR[$y][$x] = $pixel;
- $x++;
- }
- $offset++;
- } elseif ($bpp == 4) {
- $p = ord($data[$offset]);
- $pixel1 = $palette[$p >> 4];
- $pixel2 = $palette[$p & 0x0f];
- $XOR[$y][$x] = $pixel1;
- $XOR[$y][$x+1] = $pixel2;
- $x += 2;
- $offset++;
- } elseif ($bpp == 8) {
- $pixel = $palette[ord($data[$offset])];
- $XOR[$y][$x] = $pixel;
- $x += 1;
- $offset++;
- } else {
- $this->error = "bad BPP";
- return false;
- }
- }
- // End of row padding
- while ($offset & 3)
- $offset++;
- }
- // Now the "AND" image, which is 1 bit per pixel. Ignore
- // if some of our image data already had alpha values,
- // or if there isn't enough data left.
- if ($this->had_alpha ||
- ((strlen($data) - $offset) < ($height * $width / 8))) {
- // Just return what we've got
- for ($y = 0; $y < $height; $y++) {
- for ($x = 0; $x < $width; $x++) {
- imagesetpixel($img, $x, $y,
- $XOR[$y][$x]);
- }
- }
- return $img;
- }
- // Mask what we have with the "AND" image
- for ($y = $height - 1; $y >= 0; $y--) {
- $x = 0;
- while ($x < $width) {
- for ($b = 0x80;
- $b > 0 && $x < $width; $b >>= 1) {
- if (!(ord($data[$offset]) & $b)) {
- imagesetpixel($img, $x, $y,
- $XOR[$y][$x]);
- }
- $x++;
- }
- $offset++;
- }
- // End of row padding
- while ($offset & 3)
- $offset++;
- }
- return $img;
- }
- }
- ?>
|