xref: /plugin/farmer/vendor/splitbrain/php-ico/src/PhpIco.php (revision c609f1dcc91a56df760d51ba92f6e25b7289002c)
1*c609f1dcSAndreas Gohr<?php
2*c609f1dcSAndreas Gohr
3*c609f1dcSAndreas Gohr/*
4*c609f1dcSAndreas GohrCopyright 2011-2016 Chris Jean & iThemes
5*c609f1dcSAndreas GohrLicensed under GPLv2 or above
6*c609f1dcSAndreas Gohr*/
7*c609f1dcSAndreas Gohr
8*c609f1dcSAndreas Gohrnamespace splitbrain\phpico;
9*c609f1dcSAndreas Gohr
10*c609f1dcSAndreas Gohrclass PhpIco
11*c609f1dcSAndreas Gohr{
12*c609f1dcSAndreas Gohr    /**
13*c609f1dcSAndreas Gohr     * Images in the BMP format.
14*c609f1dcSAndreas Gohr     *
15*c609f1dcSAndreas Gohr     * @var array
16*c609f1dcSAndreas Gohr     */
17*c609f1dcSAndreas Gohr    private $images = array();
18*c609f1dcSAndreas Gohr
19*c609f1dcSAndreas Gohr    /**
20*c609f1dcSAndreas Gohr     * Constructor - Create a new ICO generator.
21*c609f1dcSAndreas Gohr     *
22*c609f1dcSAndreas Gohr     * If the constructor is not passed a file, a file will need to be supplied using the {@link PHP_ICO::add_image}
23*c609f1dcSAndreas Gohr     * function in order to generate an ICO file.
24*c609f1dcSAndreas Gohr     *
25*c609f1dcSAndreas Gohr     * @param string $file Optional. Path to the source image file.
26*c609f1dcSAndreas Gohr     * @param array $sizes Optional. An array of sizes (each size is an array with a width and height)
27*c609f1dcSAndreas Gohr     *                               that the source image should be rendered at in the generated ICO file.
28*c609f1dcSAndreas Gohr     *                               If sizes are not supplied, the size of the source image will be used.
29*c609f1dcSAndreas Gohr     */
30*c609f1dcSAndreas Gohr    public function __construct($file = false, $sizes = array())
31*c609f1dcSAndreas Gohr    {
32*c609f1dcSAndreas Gohr        $required_functions = array(
33*c609f1dcSAndreas Gohr            'getimagesize',
34*c609f1dcSAndreas Gohr            'imagecreatefromstring',
35*c609f1dcSAndreas Gohr            'imagecreatetruecolor',
36*c609f1dcSAndreas Gohr            'imagecolortransparent',
37*c609f1dcSAndreas Gohr            'imagecolorallocatealpha',
38*c609f1dcSAndreas Gohr            'imagealphablending',
39*c609f1dcSAndreas Gohr            'imagesavealpha',
40*c609f1dcSAndreas Gohr            'imagesx',
41*c609f1dcSAndreas Gohr            'imagesy',
42*c609f1dcSAndreas Gohr            'imagecopyresampled',
43*c609f1dcSAndreas Gohr        );
44*c609f1dcSAndreas Gohr
45*c609f1dcSAndreas Gohr        foreach ($required_functions as $function) {
46*c609f1dcSAndreas Gohr            if (!function_exists($function)) {
47*c609f1dcSAndreas Gohr                throw new \RuntimeException(
48*c609f1dcSAndreas Gohr                    "The PhpIco class was unable to find the $function function, which is part of the GD library. ".
49*c609f1dcSAndreas Gohr                    'Ensure that the system has the GD library installed and that PHP has access to it through a PHP '.
50*c609f1dcSAndreas Gohr                    'interface, such as PHP\'s GD module. Since this function was not found, the library will be '.
51*c609f1dcSAndreas Gohr                    'unable to create ICO files.'
52*c609f1dcSAndreas Gohr                );
53*c609f1dcSAndreas Gohr            }
54*c609f1dcSAndreas Gohr        }
55*c609f1dcSAndreas Gohr
56*c609f1dcSAndreas Gohr        if ($file) {
57*c609f1dcSAndreas Gohr            $this->addImage($file, $sizes);
58*c609f1dcSAndreas Gohr        }
59*c609f1dcSAndreas Gohr    }
60*c609f1dcSAndreas Gohr
61*c609f1dcSAndreas Gohr    /**
62*c609f1dcSAndreas Gohr     * Add an image to the generator.
63*c609f1dcSAndreas Gohr     *
64*c609f1dcSAndreas Gohr     * This function adds a source image to the generator. It serves two main purposes: add a source image if one was
65*c609f1dcSAndreas Gohr     * not supplied to the constructor and to add additional source images so that different images can be supplied for
66*c609f1dcSAndreas Gohr     * different sized images in the resulting ICO file. For instance, a small source image can be used for the small
67*c609f1dcSAndreas Gohr     * resolutions while a larger source image can be used for large resolutions.
68*c609f1dcSAndreas Gohr     *
69*c609f1dcSAndreas Gohr     * @param string $file Path to the source image file.
70*c609f1dcSAndreas Gohr     * @param array $sizes Optional. An array of sizes (each size is an array with a width and height) that the source
71*c609f1dcSAndreas Gohr     *                     image should be rendered at in the generated ICO file. If sizes are not supplied, the size
72*c609f1dcSAndreas Gohr     *                     of the source image will be used.
73*c609f1dcSAndreas Gohr     * @return boolean true on success and false on failure.
74*c609f1dcSAndreas Gohr     */
75*c609f1dcSAndreas Gohr    public function addImage($file, $sizes = array())
76*c609f1dcSAndreas Gohr    {
77*c609f1dcSAndreas Gohr        if (false === ($im = $this->loadImageFile($file))) {
78*c609f1dcSAndreas Gohr            return false;
79*c609f1dcSAndreas Gohr        }
80*c609f1dcSAndreas Gohr
81*c609f1dcSAndreas Gohr
82*c609f1dcSAndreas Gohr        if (empty($sizes)) {
83*c609f1dcSAndreas Gohr            $sizes = array(imagesx($im), imagesy($im));
84*c609f1dcSAndreas Gohr        }
85*c609f1dcSAndreas Gohr
86*c609f1dcSAndreas Gohr        // If just a single size was passed, put it in array.
87*c609f1dcSAndreas Gohr        if (!is_array($sizes[0])) {
88*c609f1dcSAndreas Gohr            $sizes = array($sizes);
89*c609f1dcSAndreas Gohr        }
90*c609f1dcSAndreas Gohr
91*c609f1dcSAndreas Gohr        foreach ((array)$sizes as $size) {
92*c609f1dcSAndreas Gohr            list($width, $height) = $size;
93*c609f1dcSAndreas Gohr
94*c609f1dcSAndreas Gohr            $new_im = imagecreatetruecolor($width, $height);
95*c609f1dcSAndreas Gohr
96*c609f1dcSAndreas Gohr            imagecolortransparent($new_im, imagecolorallocatealpha($new_im, 0, 0, 0, 127));
97*c609f1dcSAndreas Gohr            imagealphablending($new_im, false);
98*c609f1dcSAndreas Gohr            imagesavealpha($new_im, true);
99*c609f1dcSAndreas Gohr
100*c609f1dcSAndreas Gohr            $source_width = imagesx($im);
101*c609f1dcSAndreas Gohr            $source_height = imagesy($im);
102*c609f1dcSAndreas Gohr
103*c609f1dcSAndreas Gohr            if (false === imagecopyresampled(
104*c609f1dcSAndreas Gohr                    $new_im,
105*c609f1dcSAndreas Gohr                    $im,
106*c609f1dcSAndreas Gohr                    0,
107*c609f1dcSAndreas Gohr                    0,
108*c609f1dcSAndreas Gohr                    0,
109*c609f1dcSAndreas Gohr                    0,
110*c609f1dcSAndreas Gohr                    $width,
111*c609f1dcSAndreas Gohr                    $height,
112*c609f1dcSAndreas Gohr                    $source_width,
113*c609f1dcSAndreas Gohr                    $source_height)
114*c609f1dcSAndreas Gohr            ) {
115*c609f1dcSAndreas Gohr                continue;
116*c609f1dcSAndreas Gohr            }
117*c609f1dcSAndreas Gohr
118*c609f1dcSAndreas Gohr            $this->addImageData($new_im);
119*c609f1dcSAndreas Gohr        }
120*c609f1dcSAndreas Gohr
121*c609f1dcSAndreas Gohr        return true;
122*c609f1dcSAndreas Gohr    }
123*c609f1dcSAndreas Gohr
124*c609f1dcSAndreas Gohr    /**
125*c609f1dcSAndreas Gohr     * Write the ICO file data to a file path.
126*c609f1dcSAndreas Gohr     *
127*c609f1dcSAndreas Gohr     * @param string $file Path to save the ICO file data into.
128*c609f1dcSAndreas Gohr     * @return boolean true on success and false on failure.
129*c609f1dcSAndreas Gohr     */
130*c609f1dcSAndreas Gohr    public function saveIco($file)
131*c609f1dcSAndreas Gohr    {
132*c609f1dcSAndreas Gohr        if (false === ($data = $this->getIcoData())) {
133*c609f1dcSAndreas Gohr            return false;
134*c609f1dcSAndreas Gohr        }
135*c609f1dcSAndreas Gohr
136*c609f1dcSAndreas Gohr        if (false === ($fh = fopen($file, 'w'))) {
137*c609f1dcSAndreas Gohr            return false;
138*c609f1dcSAndreas Gohr        }
139*c609f1dcSAndreas Gohr
140*c609f1dcSAndreas Gohr        if (false === (fwrite($fh, $data))) {
141*c609f1dcSAndreas Gohr            fclose($fh);
142*c609f1dcSAndreas Gohr            return false;
143*c609f1dcSAndreas Gohr        }
144*c609f1dcSAndreas Gohr
145*c609f1dcSAndreas Gohr        fclose($fh);
146*c609f1dcSAndreas Gohr
147*c609f1dcSAndreas Gohr        return true;
148*c609f1dcSAndreas Gohr    }
149*c609f1dcSAndreas Gohr
150*c609f1dcSAndreas Gohr    /**
151*c609f1dcSAndreas Gohr     * Generate the final ICO data by creating a file header and adding the image data.
152*c609f1dcSAndreas Gohr     */
153*c609f1dcSAndreas Gohr    protected function getIcoData()
154*c609f1dcSAndreas Gohr    {
155*c609f1dcSAndreas Gohr        if (!is_array($this->images) || empty($this->images)) {
156*c609f1dcSAndreas Gohr            return false;
157*c609f1dcSAndreas Gohr        }
158*c609f1dcSAndreas Gohr
159*c609f1dcSAndreas Gohr
160*c609f1dcSAndreas Gohr        $data = pack('vvv', 0, 1, count($this->images));
161*c609f1dcSAndreas Gohr        $pixel_data = '';
162*c609f1dcSAndreas Gohr
163*c609f1dcSAndreas Gohr        $icon_dir_entry_size = 16;
164*c609f1dcSAndreas Gohr
165*c609f1dcSAndreas Gohr        $offset = 6 + ($icon_dir_entry_size * count($this->images));
166*c609f1dcSAndreas Gohr
167*c609f1dcSAndreas Gohr        foreach ($this->images as $image) {
168*c609f1dcSAndreas Gohr            $data .= pack(
169*c609f1dcSAndreas Gohr                'CCCCvvVV',
170*c609f1dcSAndreas Gohr                $image['width'],
171*c609f1dcSAndreas Gohr                $image['height'],
172*c609f1dcSAndreas Gohr                $image['color_palette_colors'],
173*c609f1dcSAndreas Gohr                0,
174*c609f1dcSAndreas Gohr                1,
175*c609f1dcSAndreas Gohr                $image['bits_per_pixel'],
176*c609f1dcSAndreas Gohr                $image['size'],
177*c609f1dcSAndreas Gohr                $offset
178*c609f1dcSAndreas Gohr            );
179*c609f1dcSAndreas Gohr            $pixel_data .= $image['data'];
180*c609f1dcSAndreas Gohr
181*c609f1dcSAndreas Gohr            $offset += $image['size'];
182*c609f1dcSAndreas Gohr        }
183*c609f1dcSAndreas Gohr
184*c609f1dcSAndreas Gohr        $data .= $pixel_data;
185*c609f1dcSAndreas Gohr        unset($pixel_data);
186*c609f1dcSAndreas Gohr
187*c609f1dcSAndreas Gohr
188*c609f1dcSAndreas Gohr        return $data;
189*c609f1dcSAndreas Gohr    }
190*c609f1dcSAndreas Gohr
191*c609f1dcSAndreas Gohr    /**
192*c609f1dcSAndreas Gohr     * Take a GD image resource and change it into a raw BMP format.
193*c609f1dcSAndreas Gohr     */
194*c609f1dcSAndreas Gohr    protected function addImageData($im)
195*c609f1dcSAndreas Gohr    {
196*c609f1dcSAndreas Gohr        $width = imagesx($im);
197*c609f1dcSAndreas Gohr        $height = imagesy($im);
198*c609f1dcSAndreas Gohr
199*c609f1dcSAndreas Gohr
200*c609f1dcSAndreas Gohr        $pixel_data = array();
201*c609f1dcSAndreas Gohr
202*c609f1dcSAndreas Gohr        $opacity_data = array();
203*c609f1dcSAndreas Gohr        $current_opacity_val = 0;
204*c609f1dcSAndreas Gohr
205*c609f1dcSAndreas Gohr        for ($y = $height - 1; $y >= 0; $y--) {
206*c609f1dcSAndreas Gohr            for ($x = 0; $x < $width; $x++) {
207*c609f1dcSAndreas Gohr                $color = imagecolorat($im, $x, $y);
208*c609f1dcSAndreas Gohr
209*c609f1dcSAndreas Gohr                $alpha = ($color & 0x7F000000) >> 24;
210*c609f1dcSAndreas Gohr                $alpha = (1 - ($alpha / 127)) * 255;
211*c609f1dcSAndreas Gohr
212*c609f1dcSAndreas Gohr                $color &= 0xFFFFFF;
213*c609f1dcSAndreas Gohr                $color |= 0xFF000000 & ($alpha << 24);
214*c609f1dcSAndreas Gohr
215*c609f1dcSAndreas Gohr                $pixel_data[] = $color;
216*c609f1dcSAndreas Gohr
217*c609f1dcSAndreas Gohr
218*c609f1dcSAndreas Gohr                $opacity = ($alpha <= 127) ? 1 : 0;
219*c609f1dcSAndreas Gohr
220*c609f1dcSAndreas Gohr                $current_opacity_val = ($current_opacity_val << 1) | $opacity;
221*c609f1dcSAndreas Gohr
222*c609f1dcSAndreas Gohr                if ((($x + 1) % 32) == 0) {
223*c609f1dcSAndreas Gohr                    $opacity_data[] = $current_opacity_val;
224*c609f1dcSAndreas Gohr                    $current_opacity_val = 0;
225*c609f1dcSAndreas Gohr                }
226*c609f1dcSAndreas Gohr            }
227*c609f1dcSAndreas Gohr
228*c609f1dcSAndreas Gohr            if (($x % 32) > 0) {
229*c609f1dcSAndreas Gohr                while (($x++ % 32) > 0) {
230*c609f1dcSAndreas Gohr                    $current_opacity_val = $current_opacity_val << 1;
231*c609f1dcSAndreas Gohr                }
232*c609f1dcSAndreas Gohr
233*c609f1dcSAndreas Gohr                $opacity_data[] = $current_opacity_val;
234*c609f1dcSAndreas Gohr                $current_opacity_val = 0;
235*c609f1dcSAndreas Gohr            }
236*c609f1dcSAndreas Gohr        }
237*c609f1dcSAndreas Gohr
238*c609f1dcSAndreas Gohr        $image_header_size = 40;
239*c609f1dcSAndreas Gohr        $color_mask_size = $width * $height * 4;
240*c609f1dcSAndreas Gohr        $opacity_mask_size = (ceil($width / 32) * 4) * $height;
241*c609f1dcSAndreas Gohr
242*c609f1dcSAndreas Gohr
243*c609f1dcSAndreas Gohr        $data = pack('VVVvvVVVVVV', 40, $width, ($height * 2), 1, 32, 0, 0, 0, 0, 0, 0);
244*c609f1dcSAndreas Gohr
245*c609f1dcSAndreas Gohr        foreach ($pixel_data as $color) {
246*c609f1dcSAndreas Gohr            $data .= pack('V', $color);
247*c609f1dcSAndreas Gohr        }
248*c609f1dcSAndreas Gohr
249*c609f1dcSAndreas Gohr        foreach ($opacity_data as $opacity) {
250*c609f1dcSAndreas Gohr            $data .= pack('N', $opacity);
251*c609f1dcSAndreas Gohr        }
252*c609f1dcSAndreas Gohr
253*c609f1dcSAndreas Gohr
254*c609f1dcSAndreas Gohr        $image = array(
255*c609f1dcSAndreas Gohr            'width' => $width,
256*c609f1dcSAndreas Gohr            'height' => $height,
257*c609f1dcSAndreas Gohr            'color_palette_colors' => 0,
258*c609f1dcSAndreas Gohr            'bits_per_pixel' => 32,
259*c609f1dcSAndreas Gohr            'size' => $image_header_size + $color_mask_size + $opacity_mask_size,
260*c609f1dcSAndreas Gohr            'data' => $data,
261*c609f1dcSAndreas Gohr        );
262*c609f1dcSAndreas Gohr
263*c609f1dcSAndreas Gohr        $this->images[] = $image;
264*c609f1dcSAndreas Gohr    }
265*c609f1dcSAndreas Gohr
266*c609f1dcSAndreas Gohr    /**
267*c609f1dcSAndreas Gohr     * Read in the source image file and convert it into a GD image resource.
268*c609f1dcSAndreas Gohr     */
269*c609f1dcSAndreas Gohr    protected function loadImageFile($file)
270*c609f1dcSAndreas Gohr    {
271*c609f1dcSAndreas Gohr        if (!is_string($file) || empty($file)) {
272*c609f1dcSAndreas Gohr            return false;
273*c609f1dcSAndreas Gohr        }
274*c609f1dcSAndreas Gohr        // Run a cheap check to verify that it is an image file.
275*c609f1dcSAndreas Gohr        if (false === ($size = getimagesize($file))) {
276*c609f1dcSAndreas Gohr            return false;
277*c609f1dcSAndreas Gohr        }
278*c609f1dcSAndreas Gohr
279*c609f1dcSAndreas Gohr        if (false === ($file_data = file_get_contents($file))) {
280*c609f1dcSAndreas Gohr            return false;
281*c609f1dcSAndreas Gohr        }
282*c609f1dcSAndreas Gohr
283*c609f1dcSAndreas Gohr        if (false === ($im = imagecreatefromstring($file_data))) {
284*c609f1dcSAndreas Gohr            return false;
285*c609f1dcSAndreas Gohr        }
286*c609f1dcSAndreas Gohr
287*c609f1dcSAndreas Gohr        unset($file_data);
288*c609f1dcSAndreas Gohr
289*c609f1dcSAndreas Gohr
290*c609f1dcSAndreas Gohr        return $im;
291*c609f1dcSAndreas Gohr    }
292*c609f1dcSAndreas Gohr}
293