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