1 | <?php |
||
2 | |||
3 | // Simple .ICO parsing. The ICO format is insanely complex and this may |
||
4 | // fail to correctly handle some technically valid files, but it works |
||
5 | // on the majority I've found. |
||
6 | // |
||
7 | // jimIcon was written in 2013 by Jim Paris <[email protected]> and is |
||
8 | // released under the terms of the CC0: |
||
9 | // |
||
10 | // To the extent possible under law, the author(s) have dedicated all |
||
11 | // copyright and related and neighboring rights to this software to |
||
12 | // the public domain worldwide. This software is distributed without |
||
13 | // any arranty. |
||
14 | // |
||
15 | // You may have received a copy of the CC0 Public Domain Dedication |
||
16 | // along with this software. If not, see |
||
17 | // http://creativecommons.org/publicdomain/zero/1.0/ |
||
18 | |||
19 | class jimIcon { |
||
20 | // Get an image color from a string |
||
21 | function get_color($str, $img) { |
||
0 ignored issues
–
show
|
|||
22 | $b = ord($str[0]); |
||
23 | $g = ord($str[1]); |
||
24 | $r = ord($str[2]); |
||
25 | if (strlen($str) > 3) { |
||
26 | $a = 127 - (ord($str[3]) / 2); |
||
27 | if ($a != 0 && $a != 127) { |
||
28 | $this->had_alpha = 1; |
||
0 ignored issues
–
show
|
|||
29 | } |
||
30 | } else { |
||
31 | $a = 0; |
||
32 | } |
||
33 | if ($a != 127) { |
||
34 | $this->all_transaprent = 0; |
||
0 ignored issues
–
show
|
|||
35 | } |
||
36 | return imagecolorallocatealpha($img, $r, $g, $b, $a); |
||
37 | } |
||
38 | |||
39 | // Given a string with the contents of an .ICO, |
||
40 | // return a GD image of the icon, or false on error. |
||
41 | function fromiconstring($ico) { |
||
0 ignored issues
–
show
|
|||
42 | $this->error = "(unknown error)"; |
||
0 ignored issues
–
show
|
|||
43 | $this->had_alpha = 0; |
||
0 ignored issues
–
show
|
|||
44 | |||
45 | // Read header |
||
46 | if (strlen($ico) < 6) { |
||
47 | $this->error = "too short"; |
||
48 | return false; |
||
49 | } |
||
50 | $h = unpack("vzero/vtype/vnum", $ico); |
||
51 | |||
52 | // Must be ICO format with at least one image |
||
53 | if ($h["zero"] != 0 || $h["type"] != 1 || $h["num"] == 0) { |
||
54 | // See if we can just parse it with GD directly |
||
55 | // if it's not ICO format; maybe it was a mislabeled |
||
56 | // PNG or something. |
||
57 | $i = @imagecreatefromstring($ico); |
||
58 | if ($i) { |
||
0 ignored issues
–
show
|
|||
59 | imagesavealpha($i, true); |
||
60 | return $i; |
||
61 | } |
||
62 | $this->error = "not ICO or other image"; |
||
63 | return false; |
||
64 | } |
||
65 | |||
66 | // Read directory entries to find the biggest image |
||
67 | $most_pixels = 0; |
||
68 | for ($i = 0; $i < $h["num"]; $i++) { |
||
69 | $entry = substr($ico, 6 + 16 * $i, 16); |
||
70 | if (!$entry || strlen($entry) < 16) { |
||
71 | continue; |
||
72 | } |
||
73 | $e = unpack("Cwidth/". |
||
74 | "Cheight/". |
||
75 | "Ccolors/". |
||
76 | "Czero/". |
||
77 | "vplanes/". |
||
78 | "vbpp/". |
||
79 | "Vsize/". |
||
80 | "Voffset/", |
||
81 | $entry); |
||
82 | if ($e["width"] == 0) { |
||
83 | $e["width"] = 256; |
||
84 | } |
||
85 | if ($e["height"] == 0) { |
||
86 | $e["height"] = 256; |
||
87 | } |
||
88 | if ($e["zero"] != 0) { |
||
89 | $this->error = "nonzero reserved field"; |
||
90 | return false; |
||
91 | } |
||
92 | $pixels = $e["width"] * $e["height"]; |
||
93 | if ($pixels > $most_pixels) { |
||
94 | $most_pixels = $pixels; |
||
95 | $most = $e; |
||
96 | } |
||
97 | } |
||
98 | if ($most_pixels == 0) { |
||
0 ignored issues
–
show
|
|||
99 | $this->error = "no pixels"; |
||
100 | return false; |
||
101 | } |
||
102 | $e = $most; |
||
103 | |||
104 | // Extract image data |
||
105 | $data = substr($ico, $e["offset"], $e["size"]); |
||
106 | if (!$data || strlen($data) != $e["size"]) { |
||
107 | $this->error = "bad image data"; |
||
108 | return false; |
||
109 | } |
||
110 | |||
111 | // See if we can parse it (might be PNG format here) |
||
112 | $i = @imagecreatefromstring($data); |
||
113 | if ($i) { |
||
114 | imagesavealpha($img, true); |
||
115 | return $i; |
||
116 | } |
||
117 | |||
118 | // Must be a BMP. Parse it ourselves. |
||
119 | $img = imagecreatetruecolor($e["width"], $e["height"]); |
||
120 | imagesavealpha($img, true); |
||
121 | $bg = imagecolorallocatealpha($img, 255, 0, 0, 127); |
||
122 | imagefill($img, 0, 0, $bg); |
||
123 | |||
124 | // Skip over the BITMAPCOREHEADER or BITMAPINFOHEADER; |
||
125 | // we'll just assume the palette and pixel data follow |
||
126 | // in the most obvious format as described by the icon |
||
127 | // directory entry. |
||
128 | $bitmapinfo = unpack("Vsize", $data); |
||
129 | if ($bitmapinfo["size"] == 40) { |
||
130 | $info = unpack("Vsize/". |
||
131 | "Vwidth/". |
||
132 | "Vheight/". |
||
133 | "vplanes/". |
||
134 | "vbpp/". |
||
135 | "Vcompress/". |
||
136 | "Vsize/". |
||
137 | "Vxres/". |
||
138 | "Vyres/". |
||
139 | "Vpalcolors/". |
||
140 | "Vimpcolors/", $data); |
||
141 | if ($e["bpp"] == 0) { |
||
142 | $e["bpp"] = $info["bpp"]; |
||
143 | } |
||
144 | } |
||
145 | $data = substr($data, $bitmapinfo["size"]); |
||
146 | |||
147 | $height = $e["height"]; |
||
148 | $width = $e["width"]; |
||
149 | $bpp = $e["bpp"]; |
||
150 | |||
151 | // For indexed images, we only support 1, 4, or 8 BPP |
||
152 | switch ($bpp) { |
||
153 | case 1: |
||
154 | case 4: |
||
155 | case 8: |
||
156 | $indexed = 1; |
||
157 | break; |
||
158 | case 24: |
||
159 | case 32: |
||
160 | $indexed = 0; |
||
161 | break; |
||
162 | default: |
||
163 | $this->error = "bad BPP $bpp"; |
||
164 | return false; |
||
165 | } |
||
166 | |||
167 | $offset = 0; |
||
168 | if ($indexed) { |
||
169 | $palette = array(); |
||
170 | $this->all_transparent = 1; |
||
171 | for ($i = 0; $i < (1 << $bpp); $i++) { |
||
172 | $entry = substr($data, $i * 4, 4); |
||
173 | $palette[$i] = $this->get_color($entry, $img); |
||
174 | } |
||
175 | $offset = $i * 4; |
||
176 | |||
177 | // Hack for some icons: if everything was transparent, |
||
178 | // discard alpha channel. |
||
179 | if ($this->all_transparent) { |
||
180 | for ($i = 0; $i < (1 << $bpp); $i++) { |
||
181 | $palette[$i] &= 0xffffff; |
||
182 | } |
||
183 | } |
||
184 | } |
||
185 | |||
186 | // Assume image data follows in bottom-up order. |
||
187 | // First the "XOR" image |
||
188 | if ((strlen($data) - $offset) < ($bpp * $height * $width / 8)) { |
||
189 | $this->error = "short data"; |
||
190 | return false; |
||
191 | } |
||
192 | $XOR = array(); |
||
193 | for ($y = $height - 1; $y >= 0; $y--) { |
||
194 | $x = 0; |
||
195 | while ($x < $width) { |
||
196 | if (!$indexed) { |
||
197 | $bytes = $bpp / 8; |
||
198 | $entry = substr($data, $offset, $bytes); |
||
199 | $pixel = $this->get_color($entry, $img); |
||
200 | $XOR[$y][$x] = $pixel; |
||
201 | $x++; |
||
202 | $offset += $bytes; |
||
203 | } elseif ($bpp == 1) { |
||
204 | $p = ord($data[$offset]); |
||
205 | for ($b = 0x80; $b > 0; $b >>= 1) { |
||
206 | if ($p & $b) { |
||
207 | $pixel = $palette[1]; |
||
208 | } else { |
||
209 | $pixel = $palette[0]; |
||
210 | } |
||
211 | $XOR[$y][$x] = $pixel; |
||
212 | $x++; |
||
213 | } |
||
214 | $offset++; |
||
215 | } elseif ($bpp == 4) { |
||
216 | $p = ord($data[$offset]); |
||
217 | $pixel1 = $palette[$p >> 4]; |
||
218 | $pixel2 = $palette[$p & 0x0f]; |
||
219 | $XOR[$y][$x] = $pixel1; |
||
220 | $XOR[$y][$x + 1] = $pixel2; |
||
221 | $x += 2; |
||
222 | $offset++; |
||
223 | } elseif ($bpp == 8) { |
||
224 | $pixel = $palette[ord($data[$offset])]; |
||
225 | $XOR[$y][$x] = $pixel; |
||
226 | $x += 1; |
||
227 | $offset++; |
||
228 | } else { |
||
229 | $this->error = "bad BPP"; |
||
230 | return false; |
||
231 | } |
||
232 | } |
||
233 | // End of row padding |
||
234 | while ($offset & 3) { |
||
235 | $offset++; |
||
236 | } |
||
237 | } |
||
238 | |||
239 | // Now the "AND" image, which is 1 bit per pixel. Ignore |
||
240 | // if some of our image data already had alpha values, |
||
241 | // or if there isn't enough data left. |
||
242 | if ($this->had_alpha || |
||
243 | ((strlen($data) - $offset) < ($height * $width / 8))) { |
||
244 | // Just return what we've got |
||
245 | for ($y = 0; $y < $height; $y++) { |
||
246 | for ($x = 0; $x < $width; $x++) { |
||
247 | imagesetpixel($img, $x, $y, |
||
248 | $XOR[$y][$x]); |
||
249 | } |
||
250 | } |
||
251 | return $img; |
||
252 | } |
||
253 | |||
254 | // Mask what we have with the "AND" image |
||
255 | for ($y = $height - 1; $y >= 0; $y--) { |
||
256 | $x = 0; |
||
257 | while ($x < $width) { |
||
258 | for ($b = 0x80; |
||
259 | $b > 0 && $x < $width; $b >>= 1) { |
||
260 | if (!(ord($data[$offset]) & $b)) { |
||
261 | imagesetpixel($img, $x, $y, |
||
262 | $XOR[$y][$x]); |
||
263 | } |
||
264 | $x++; |
||
265 | } |
||
266 | $offset++; |
||
267 | } |
||
268 | |||
269 | // End of row padding |
||
270 | while ($offset & 3) { |
||
271 | $offset++; |
||
272 | } |
||
273 | } |
||
274 | return $img; |
||
275 | } |
||
276 | } |
||
277 | ?> |
||
0 ignored issues
–
show
It is not recommended to use PHP's closing tag
?> in files other than templates.
Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore. A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever. ![]() |
Adding explicit visibility (
private
,protected
, orpublic
) is generally recommend to communicate to other developers how, and from where this method is intended to be used.