Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like Ico often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use Ico, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
8 | class Ico |
||
9 | { |
||
10 | /** |
||
11 | * Background color on icon extraction. |
||
12 | * @var array(R, G, B) = array(255, 255, 255) |
||
13 | */ |
||
14 | public $bgcolor = [255, 255, 255]; |
||
15 | |||
16 | /** |
||
17 | * @var bool Is background color transparent? |
||
18 | */ |
||
19 | public $bgcolorTransparent = false; |
||
20 | |||
21 | private $filename; |
||
22 | private $ico; |
||
23 | private $iconDirEntry; |
||
24 | |||
25 | /** |
||
26 | * Constructor |
||
27 | * |
||
28 | * @param string $path optional path to ICO file |
||
29 | */ |
||
30 | 8 | public function __construct($path = '') |
|
36 | |||
37 | /** |
||
38 | * Load an ICO file (don't need to call this is if fill the |
||
39 | * parameter in the class constructor). |
||
40 | * |
||
41 | * @param string $path Path to ICO file |
||
42 | * |
||
43 | * @return bool Success |
||
44 | */ |
||
45 | 8 | public function loadFile($path) |
|
50 | |||
51 | /** |
||
52 | * Load an ICO data. If you prefer to open the file |
||
53 | * and return the binary data you can use this function |
||
54 | * directly. Otherwise use loadFile() instead. |
||
55 | * |
||
56 | * @param string $data Binary data of ICO file |
||
57 | * |
||
58 | * @return bool Success |
||
59 | */ |
||
60 | 8 | public function loadData($data) |
|
61 | { |
||
62 | 8 | $this->iconDirEntry = []; |
|
63 | |||
64 | //extract ICONDIR header |
||
65 | 8 | $icodata = unpack('SReserved/SType/SCount', $data); |
|
66 | 8 | $this->ico = $icodata; |
|
67 | 8 | $data = substr($data, 6); |
|
68 | |||
69 | //extract ICONDIRENTRY structures |
||
70 | 8 | $data = $this->extractIconDirEntries($data); |
|
71 | |||
72 | // Extract additional headers for each extracted ICONDIRENTRY |
||
73 | 8 | $iconCount = count($this->iconDirEntry); |
|
74 | 8 | for ($i = 0; $i < $iconCount; ++$i) { |
|
75 | 8 | $signature = unpack('LFourCC', substr($data, $this->iconDirEntry[$i]['FileOffset'], 4)); |
|
76 | |||
77 | 8 | if ($signature['FourCC'] == 0x474e5089) { |
|
78 | 2 | $this->extractPng($i, $data); |
|
79 | 2 | } else { |
|
80 | 8 | $this->extractBmp($i, $data); |
|
81 | } |
||
82 | 8 | } |
|
83 | 8 | return true; |
|
84 | } |
||
85 | |||
86 | 8 | private function extractIconDirEntries($data) |
|
87 | { |
||
88 | 8 | for ($i = 0; $i < $this->ico['Count']; ++$i) { |
|
89 | 8 | $icodata = unpack('CWidth/CHeight/CColorCount/CReserved/SPlanes/SBitCount/LSizeInBytes/LFileOffset', $data); |
|
90 | 8 | $icodata['FileOffset'] -= ($this->ico['Count'] * 16) + 6; |
|
91 | 8 | if ($icodata['ColorCount'] == 0) { |
|
92 | 6 | $icodata['ColorCount'] = 256; |
|
93 | 6 | } |
|
94 | 8 | if ($icodata['Width'] == 0) { |
|
95 | 2 | $icodata['Width'] = 256; |
|
96 | 2 | } |
|
97 | 8 | if ($icodata['Height'] == 0) { |
|
98 | 2 | $icodata['Height'] = 256; |
|
99 | 2 | } |
|
100 | 8 | $this->iconDirEntry[] = $icodata; |
|
101 | |||
102 | 8 | $data = substr($data, 16); |
|
103 | 8 | } |
|
104 | |||
105 | 8 | return $data; |
|
106 | } |
||
107 | |||
108 | 2 | private function extractPng($i, $data) |
|
114 | |||
115 | 8 | private function extractBmp($i, $data) |
|
116 | { |
||
117 | 8 | $bitmapInfoHeader = unpack( |
|
118 | 'LSize/LWidth/LHeight/SPlanes/SBitCount/LCompression/LImageSize/' . |
||
119 | 8 | 'LXpixelsPerM/LYpixelsPerM/LColorsUsed/LColorsImportant', |
|
120 | 8 | substr($data, $this->iconDirEntry[$i]['FileOffset']) |
|
121 | 8 | ); |
|
122 | |||
123 | 8 | $this->iconDirEntry[$i]['header'] = $bitmapInfoHeader; |
|
124 | 8 | $this->iconDirEntry[$i]['colors'] = []; |
|
125 | 8 | $this->iconDirEntry[$i]['BitCount'] = $this->iconDirEntry[$i]['header']['BitCount']; |
|
126 | |||
127 | 8 | switch ($this->iconDirEntry[$i]['BitCount']) { |
|
128 | 8 | case 32: |
|
129 | 8 | case 24: |
|
130 | 5 | $this->extractTrueColorImageData($i, $data); |
|
131 | 5 | break; |
|
132 | 5 | case 8: |
|
133 | 5 | case 4: |
|
134 | 4 | $this->extractPaletteImageData($i, $data); |
|
135 | 4 | break; |
|
136 | 1 | case 1: |
|
137 | 1 | $this->extractMonoImageData($i, $data); |
|
138 | 1 | break; |
|
139 | 8 | } |
|
140 | 8 | $this->iconDirEntry[$i]['data_length'] = strlen($this->iconDirEntry[$i]['data']); |
|
141 | 8 | } |
|
142 | |||
143 | 5 | private function extractTrueColorImageData($i, $data) |
|
144 | { |
||
145 | 5 | $length = $this->iconDirEntry[$i]['header']['Width'] * |
|
146 | 5 | $this->iconDirEntry[$i]['header']['Height'] * |
|
147 | 5 | ($this->iconDirEntry[$i]['BitCount'] / 8); |
|
148 | 5 | $this->iconDirEntry[$i]['data'] = substr( |
|
149 | 5 | $data, |
|
150 | 5 | $this->iconDirEntry[$i]['FileOffset'] + $this->iconDirEntry[$i]['header']['Size'], |
|
151 | $length |
||
152 | 5 | ); |
|
153 | 5 | } |
|
154 | |||
155 | 4 | private function extractPaletteImageData($i, $data) |
|
156 | { |
||
157 | 4 | $icodata = substr( |
|
158 | 4 | $data, |
|
159 | 4 | $this->iconDirEntry[$i]['FileOffset'] + $this->iconDirEntry[$i]['header']['Size'], |
|
160 | 4 | $this->iconDirEntry[$i]['ColorCount'] * 4 |
|
161 | 4 | ); |
|
162 | 4 | $offset = 0; |
|
163 | 4 | for ($j = 0; $j < $this->iconDirEntry[$i]['ColorCount']; ++$j) { |
|
164 | 4 | $this->iconDirEntry[$i]['colors'][] = [ |
|
165 | 4 | 'blue' => ord($icodata[$offset]), |
|
166 | 4 | 'green' => ord($icodata[$offset + 1]), |
|
167 | 4 | 'red' => ord($icodata[$offset + 2]), |
|
168 | 4 | 'reserved' => ord($icodata[$offset + 3]), |
|
169 | ]; |
||
170 | 4 | $offset += 4; |
|
171 | 4 | } |
|
172 | 4 | $length = $this->iconDirEntry[$i]['header']['Width'] * |
|
173 | 4 | $this->iconDirEntry[$i]['header']['Height'] * |
|
174 | 4 | (1 + $this->iconDirEntry[$i]['BitCount']) / $this->iconDirEntry[$i]['BitCount']; |
|
175 | 4 | $this->iconDirEntry[$i]['data'] = substr( |
|
176 | 4 | $data, |
|
177 | 4 | $this->iconDirEntry[$i]['FileOffset'] + |
|
178 | 4 | ($this->iconDirEntry[$i]['ColorCount'] * 4) + |
|
179 | 4 | $this->iconDirEntry[$i]['header']['Size'], |
|
180 | $length |
||
181 | 4 | ); |
|
182 | 4 | } |
|
183 | |||
184 | 1 | private function extractMonoImageData($i, $data) |
|
185 | { |
||
186 | 1 | $icodata = substr( |
|
187 | 1 | $data, |
|
188 | 1 | $this->iconDirEntry[$i]['FileOffset'] + $this->iconDirEntry[$i]['header']['Size'], |
|
189 | 1 | $this->iconDirEntry[$i]['ColorCount'] * 4 |
|
190 | 1 | ); |
|
191 | |||
192 | 1 | $this->iconDirEntry[$i]['colors'][] = [ |
|
193 | 1 | 'blue' => ord($icodata[0]), |
|
194 | 1 | 'green' => ord($icodata[1]), |
|
195 | 1 | 'red' => ord($icodata[2]), |
|
196 | 1 | 'reserved' => ord($icodata[3]), |
|
197 | ]; |
||
198 | 1 | $this->iconDirEntry[$i]['colors'][] = [ |
|
199 | 1 | 'blue' => ord($icodata[4]), |
|
200 | 1 | 'green' => ord($icodata[5]), |
|
201 | 1 | 'red' => ord($icodata[6]), |
|
202 | 1 | 'reserved' => ord($icodata[7]), |
|
203 | ]; |
||
204 | |||
205 | 1 | $length = $this->iconDirEntry[$i]['header']['Width'] * $this->iconDirEntry[$i]['header']['Height'] / 8; |
|
206 | 1 | $this->iconDirEntry[$i]['data'] = substr( |
|
207 | 1 | $data, |
|
208 | 1 | $this->iconDirEntry[$i]['FileOffset'] + $this->iconDirEntry[$i]['header']['Size'] + 8, |
|
209 | $length |
||
210 | 1 | ); |
|
211 | 1 | } |
|
212 | |||
213 | /** |
||
214 | * Return the total icons extracted at the moment. |
||
215 | * |
||
216 | * @return int Total icons |
||
217 | */ |
||
218 | 7 | public function getTotalIcons() |
|
222 | |||
223 | /** |
||
224 | * Return the icon header corresponding to that index. |
||
225 | * |
||
226 | * @param int $index Icon index |
||
227 | * |
||
228 | * @return resource|bool Icon header or false |
||
229 | */ |
||
230 | 8 | public function getIconInfo($index) |
|
238 | |||
239 | /** |
||
240 | * Changes background color of extraction. You can set |
||
241 | * the 3 color components or set $red = '#xxxxxx' (HTML format) |
||
242 | * and leave all other blanks. |
||
243 | * |
||
244 | * @param int $red Red component |
||
245 | * @param int $green Green component |
||
246 | * @param int $blue Blue component |
||
247 | */ |
||
248 | 6 | public function setBackground($red = 255, $green = 255, $blue = 255) |
|
249 | { |
||
250 | 6 | if (is_string($red) && preg_match('/^\#[0-9a-f]{6}$/', $red)) { |
|
251 | 6 | $green = hexdec($red[3] . $red[4]); |
|
252 | 6 | $blue = hexdec($red[5] . $red[6]); |
|
253 | 6 | $red = hexdec($red[1] . $red[2]); |
|
254 | 6 | } |
|
255 | |||
256 | 6 | $this->bgcolor = [$red, $green, $blue]; |
|
257 | 6 | } |
|
258 | |||
259 | /** |
||
260 | * Set background color to be saved as transparent. |
||
261 | * |
||
262 | * @param bool $transparent Is Transparent or not |
||
263 | * |
||
264 | * @return bool Is Transparent or not |
||
265 | */ |
||
266 | 3 | public function setBackgroundTransparent($transparent = true) |
|
270 | |||
271 | /** |
||
272 | * Return an image resource with the icon stored |
||
273 | * on the $index position of the ICO file. |
||
274 | * |
||
275 | * @param int $index Position of the icon inside ICO |
||
276 | * |
||
277 | * @return resource|bool Image resource |
||
278 | **/ |
||
279 | 8 | public function getImage($index) |
|
291 | |||
292 | private function getPngImage($index) |
||
297 | |||
298 | 8 | private function getBmpImage($index) |
|
299 | { |
||
300 | // create image filled with desired background color |
||
301 | 8 | $w=$this->iconDirEntry[$index]['Width']; |
|
302 | 8 | $h=$this->iconDirEntry[$index]['Height']; |
|
303 | 8 | $im = imagecreatetruecolor($w, $h); |
|
304 | |||
305 | 8 | if ($this->bgcolorTransparent) { |
|
306 | 3 | imagealphablending($im, false); |
|
307 | 3 | $bgcolor=$this->allocateColor($im, $this->bgcolor[0], $this->bgcolor[1], $this->bgcolor[2], 127); |
|
308 | 3 | imagefilledrectangle($im, 0, 0, $w, $h, $bgcolor); |
|
309 | 3 | imagesavealpha($im, true); |
|
310 | 3 | } else { |
|
311 | 5 | $bgcolor = $this->allocateColor($im, $this->bgcolor[0], $this->bgcolor[1], $this->bgcolor[2]); |
|
312 | 5 | imagefilledrectangle($im, 0, 0, $w, $h, $bgcolor); |
|
313 | } |
||
314 | |||
315 | // now paint pixels based on bit count |
||
316 | 8 | switch ($this->iconDirEntry[$index]['BitCount']) { |
|
317 | 8 | case 32: |
|
318 | 3 | $this->render32bit($this->iconDirEntry[$index], $im); |
|
319 | 3 | break; |
|
320 | 5 | case 24: |
|
321 | 1 | $this->render24bit($this->iconDirEntry[$index], $im); |
|
322 | 1 | break; |
|
323 | 4 | case 8: |
|
324 | 2 | $this->render8bit($this->iconDirEntry[$index], $im); |
|
325 | 2 | break; |
|
326 | 2 | case 4: |
|
327 | 1 | $this->render4bit($this->iconDirEntry[$index], $im); |
|
328 | 1 | break; |
|
329 | 1 | case 1: |
|
330 | 1 | $this->render1bit($this->iconDirEntry[$index], $im); |
|
331 | 1 | break; |
|
332 | 8 | } |
|
333 | |||
334 | 8 | return $im; |
|
335 | } |
||
336 | |||
337 | /** |
||
338 | * Allocate a color on $im resource. This function prevents |
||
339 | * from allocating same colors on the same pallete. Instead |
||
340 | * if it finds that the color is already allocated, it only |
||
341 | * returns the index to that color. |
||
342 | * It supports alpha channel. |
||
343 | * |
||
344 | * @param resource $im Image resource |
||
345 | * @param int $red Red component |
||
346 | * @param int $green Green component |
||
347 | * @param int $blue Blue component |
||
348 | * @param int $alpha Alpha channel |
||
349 | * |
||
350 | * @return int Color index |
||
351 | */ |
||
352 | 8 | View Code Duplication | private function allocateColor($im, $red, $green, $blue, $alpha = 0) |
361 | |||
362 | 3 | private function render32bit($metadata, $im) |
|
363 | { |
||
364 | /** |
||
365 | * 32 bits: 4 bytes per pixel [ B | G | R | ALPHA ]. |
||
366 | **/ |
||
367 | 3 | $offset = 0; |
|
368 | 3 | $binary = $metadata['data']; |
|
369 | |||
386 | |||
387 | 1 | private function render24bit($metadata, $im) |
|
412 | |||
413 | 5 | private function buildMaskBits($metadata) |
|
437 | |||
438 | 2 | private function render8bit($metadata, $im) |
|
457 | |||
458 | 4 | private function buildPalette($metadata, $im) |
|
475 | |||
476 | 1 | private function render4bit($metadata, $im) |
|
503 | |||
504 | 1 | private function render1bit($metadata, $im) |
|
528 | } |
||
529 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.