Skip to content

Commit 2a1173f

Browse files
committed
Releasing PHP ICO on GitHub
1 parent 0ca9a0e commit 2a1173f

File tree

4 files changed

+707
-3
lines changed

4 files changed

+707
-3
lines changed

README.md

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,79 @@
1-
php-ico
2-
=======
1+
PHP ICO - The PHP ICO Generator
2+
===============================
33

4-
PHP ICO - The PHP ICO Generator
4+
PHP ICO provides an easy-to-use PHP class that can be used to generate valid [ICO files](http://en.wikipedia.org/wiki/ICO_%28file_format%29). Note that these are not simply BMP, GIF, or PNG formatted images with an extension of ico, the images generated by this class are fully valid ICO-formatted image files.
5+
6+
The class uses the GD library for reading an image from the source file and uses pure PHP for generating the ICO file format. In theory, any image format that GD can read can be used as a source image to generate the ICO file. I have tested this library with JPEG, GIF, and PNG images with great results. If an animated GIF is supplied, the first frame will be used to generate the ICO image.
7+
8+
ICO Format Details
9+
------------------
10+
11+
The primary goal of creating this class was to have a simple-to-use and reliable method of generating ICO files for use as [favicons](http://en.wikipedia.org/wiki/Favicon) in websites. This goal drove much of the development and is the reason for some of the limitations in the resulting functionality.
12+
13+
ICO files support two different ways of encoding the actual image data: the [Windows BMP](http://en.wikipedia.org/wiki/BMP_file_format) format and the [PNG](http://en.wikipedia.org/wiki/Portable_Network_Graphics) format. Since support for the PNG format was introduced in Windows Vista and both older and newer versions of Windows support the BMP enocoding, the BMP encoding method was used.
14+
15+
Images are encoded using 32 bits per pixel. This allows for alpha channel information in the resulting images. Support for 32 bit images was added in Windows XP. This means that older versions of Windows are not likely to display the ICO images properly, if at all. The generated images have not been tested on versions of Windows predating XP.
16+
17+
Usage
18+
-----
19+
20+
The following is a very basic example of using the PHP ICO library:
21+
22+
require( dirname( __FILE__ ) . '/class-php-ico.php' );
23+
24+
$source = dirname( __FILE__ ) . '/example.gif';
25+
$destination = dirname( __FILE__ ) . '/example.ico';
26+
27+
$ico_lib = new PHP_ICO( $source );
28+
$ico_lib->save_ico( $destination );
29+
30+
It takes a source file named `example.gif` and produce an output ICO file named `example.ico`. `example.ico` will contain a single image that is the same size as the source image.
31+
32+
The ICO file format is capable of holding multiple images, each of a different size. The PHP ICO library opens up this feature of the ICO format as shown in the following example:
33+
34+
require( dirname( __FILE__ ) . '/class-php-ico.php' );
35+
36+
$source = dirname( __FILE__ ) . '/example.gif';
37+
$destination = dirname( __FILE__ ) . '/example.ico';
38+
39+
$ico_lib = new PHP_ICO( $source, array( array( 32, 32 ), array( 64, 64 ) ) );
40+
$ico_lib->save_ico( $destination );
41+
42+
As with the previous example, this example produces `example.ico` from the `example.gif` source file. In this example, sizes were passed to the constructor that result in the `example.ico` file containing two images: one that is 32x32 and one that is 64x64. Since the same source image is used for each of the contained images, each contained image is simply the source image scaled to the new dimensions.
43+
44+
Using different source images for the different sizes contained inside an ICO file can create much better results. For instance, you can use a high-quality image for higher-resolution images and use smaller images that are tailored for their specific dimensions for the smaller sizes. The following example shows how the PHP ICO library can be used to create such ICO files:
45+
46+
require( dirname( __FILE__ ) . '/class-php-ico.php' );
47+
48+
$destination = dirname( __FILE__ ) . '/example.ico';
49+
50+
$ico_lib = new PHP_ICO();
51+
52+
$ico_lib->add_image( dirname( __FILE__ ) . '/example-small.gif', array( array( 16, 16 ), array( 24, 24 ), array( 32, 32 ) ) );
53+
$ico_lib->add_image( dirname( __FILE__ ) . '/example-medium.gif', array( array( 48, 48 ), array( 96, 96 ) ) );
54+
$ico_lib->add_image( dirname( __FILE__ ) . '/example-large.gif', array( array( 128, 128 ) ) );
55+
56+
$ico_lib->save_ico( $destination );
57+
58+
This example creates a single ICO file named `example.ico` just as the previous examples. The difference is that this example used multiple source images to generate the final ICO images. The final ICO image contains a total of six images: 16x16, 24x24, and 32x32 images from `example-small.gif`; 48x48 and 96x96 images from `example-medium.gif`; and a 128x128 image from `example-large.gif`.
59+
60+
By using this feature of supplying multiple source images with specific sizes for each, you can generate ICO files that have very high-quality images at each supplied resolution.
61+
62+
Since the PHP ICO library was created to generate favicon files, the following example shows how to generate a favicon ICO file with all the needed dimensions from a single source image:
63+
64+
require( dirname( __FILE__ ) . '/class-php-ico.php' );
65+
66+
$source = dirname( __FILE__ ) . '/example.gif';
67+
$destination = dirname( __FILE__ ) . '/example.ico';
68+
69+
$sizes = array(
70+
array( 16, 16 ),
71+
array( 24, 24 ),
72+
array( 32, 32 ),
73+
array( 48, 48 ),
74+
);
75+
76+
$ico_lib = new PHP_ICO( $source, $sizes );
77+
$ico_lib->save_ico( $destination );
78+
79+
I've found that the 16x16, 24x24, 32x32, and 48x48 image sizes cover all the sizes that browsers and Windows will try to use a favicon image for. The different sizes come from using the favicon for various browser icons, bookmarks, and various other places.

changelog

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
1.0.0 - 2011-08-04
2+
Initial version.
3+
1.0.1 - 2012-10-01
4+
Added requirements checks.
5+
1.0.2 - 2013-01-07
6+
Added bug fix that caused source images with transparency to have artifacts when output at the same dimensions as the source.

class-php-ico.php

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

0 commit comments

Comments
 (0)