Issues (1270)

lib/jimIcon.php (2 issues)

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) {
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;
29
                        }
30
                } else {
31
                        $a = 0;
32
                }
33
                if ($a != 127) {
34
                                        $this->all_transaprent = 0;
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) {
42
                $this->error = "(unknown error)";
43
                $this->had_alpha = 0;
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
$i is of type false|resource, thus it always evaluated to false.
Loading history...
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
The condition $most_pixels == 0 is always true.
Loading history...
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
?>