1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Elphin\IcoFileLoader; |
4
|
|
|
|
5
|
|
|
/** |
6
|
|
|
* GdRenderer renders an IconImage to a gd resource |
7
|
|
|
* |
8
|
|
|
* @package Elphin\IcoFileLoader |
9
|
|
|
*/ |
10
|
|
|
class GdRenderer implements RendererInterface |
11
|
|
|
{ |
12
|
7 |
|
public function render(IconImage $img, array $opts = null) |
13
|
|
|
{ |
14
|
7 |
|
$opts = $this->initOptions($img, $opts); |
15
|
|
|
|
16
|
7 |
|
if ($img->isPng()) { |
17
|
1 |
|
$gd = $this->renderPngImage($img, $opts['background']); |
18
|
1 |
|
} else { |
19
|
6 |
|
$gd = $this->renderBmpImage($img, $opts['background']); |
20
|
|
|
} |
21
|
|
|
|
22
|
7 |
|
if ((imagesx($gd) != $opts['w']) && (imagesy($gd) != $opts['h'])) { |
23
|
1 |
|
$gd = $this->resize($gd, $opts['w'], $opts['h']); |
24
|
1 |
|
} |
25
|
|
|
|
26
|
7 |
|
return $gd; |
27
|
|
|
} |
28
|
|
|
|
29
|
7 |
|
protected function initOptions(IconImage $img, $opts) |
30
|
|
|
{ |
31
|
7 |
|
$opts = is_array($opts) ? $opts : []; |
32
|
7 |
|
$opts['w'] = isset($opts['w']) ? $opts['w'] : $img->width; |
33
|
7 |
|
$opts['h'] = isset($opts['h']) ? $opts['h'] : $img->height; |
34
|
7 |
|
$opts['background'] = isset($opts['background']) ? $opts['background'] : null; |
35
|
7 |
|
return $opts; |
36
|
|
|
} |
37
|
|
|
|
38
|
1 |
|
protected function resize($gd, $w, $h) |
39
|
|
|
{ |
40
|
|
|
//TODO - imagescale not available in hhvm it seems |
41
|
1 |
|
$resized = imagescale($gd, $w, $h, IMG_BICUBIC_FIXED); |
42
|
1 |
|
imagedestroy($gd); |
43
|
1 |
|
return $resized; |
44
|
|
|
} |
45
|
|
|
|
46
|
1 |
|
protected function renderPngImage(IconImage $img, $hexBackgroundColor) |
47
|
|
|
{ |
48
|
1 |
|
$im = imagecreatefromstring($img->pngData); |
49
|
1 |
|
imagesavealpha($im, true); |
50
|
|
|
|
51
|
1 |
|
if (!is_null($hexBackgroundColor)) { |
52
|
1 |
|
$w = $img->width; |
53
|
1 |
|
$h = $img->height; |
54
|
|
|
|
55
|
1 |
|
$gd = imagecreatetruecolor($w, $h); |
56
|
1 |
|
$col = $this->parseHexColor($hexBackgroundColor); |
57
|
1 |
|
$colVal = $this->allocateColor($gd, $col[0], $col[1], $col[2]); |
58
|
1 |
|
imagefilledrectangle($gd, 0, 0, $w, $h, $colVal); |
59
|
1 |
|
imagecopy($gd, $im, 0, 0, 0, 0, $w, $h); |
60
|
1 |
|
imagedestroy($im); |
61
|
1 |
|
$im = $gd; |
62
|
1 |
|
} |
63
|
1 |
|
return $im; |
64
|
|
|
} |
65
|
|
|
|
66
|
6 |
|
protected function renderBmpImage(IconImage $img, $hexBackgroundColor = null) |
67
|
|
|
{ |
68
|
|
|
// create image filled with desired background color |
69
|
6 |
|
$w = $img->width; |
70
|
6 |
|
$h = $img->height; |
71
|
6 |
|
$gd = imagecreatetruecolor($w, $h); |
72
|
|
|
|
73
|
6 |
|
if (is_null($hexBackgroundColor)) { |
74
|
1 |
|
imagealphablending($gd, false); |
75
|
1 |
|
$colVal = $this->allocateColor($gd, 255, 255, 255, 127); |
76
|
1 |
|
imagefilledrectangle($gd, 0, 0, $w, $h, $colVal); |
77
|
1 |
|
imagesavealpha($gd, true); |
78
|
1 |
|
} else { |
79
|
5 |
|
$col = $this->parseHexColor($hexBackgroundColor); |
80
|
5 |
|
$colVal = $this->allocateColor($gd, $col[0], $col[1], $col[2]); |
81
|
5 |
|
imagefilledrectangle($gd, 0, 0, $w, $h, $colVal); |
82
|
|
|
} |
83
|
|
|
|
84
|
|
|
// now paint pixels based on bit count |
85
|
6 |
|
switch ($img->bitCount) { |
86
|
6 |
|
case 32: |
87
|
2 |
|
$this->render32bit($img, $gd); |
88
|
2 |
|
break; |
89
|
4 |
|
case 24: |
90
|
1 |
|
$this->render24bit($img, $gd); |
91
|
1 |
|
break; |
92
|
3 |
|
case 8: |
93
|
1 |
|
$this->render8bit($img, $gd); |
94
|
1 |
|
break; |
95
|
2 |
|
case 4: |
96
|
1 |
|
$this->render4bit($img, $gd); |
97
|
1 |
|
break; |
98
|
1 |
|
case 1: |
99
|
1 |
|
$this->render1bit($img, $gd); |
100
|
1 |
|
break; |
101
|
6 |
|
} |
102
|
|
|
|
103
|
6 |
|
return $gd; |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* Allocate a color on $gd resource. This function prevents |
108
|
|
|
* from allocating same colors on the same palette. Instead |
109
|
|
|
* if it finds that the color is already allocated, it only |
110
|
|
|
* returns the index to that color. |
111
|
|
|
* It supports alpha channel. |
112
|
|
|
* |
113
|
|
|
* @param resource $gd gd image resource |
114
|
|
|
* @param int $red Red component |
115
|
|
|
* @param int $green Green component |
116
|
|
|
* @param int $blue Blue component |
117
|
|
|
* @param int $alpha Alpha channel |
118
|
|
|
* |
119
|
|
|
* @return int Color index |
120
|
|
|
*/ |
121
|
7 |
View Code Duplication |
private function allocateColor($gd, $red, $green, $blue, $alpha = 0) |
|
|
|
|
122
|
|
|
{ |
123
|
7 |
|
$c = imagecolorexactalpha($gd, $red, $green, $blue, $alpha); |
124
|
7 |
|
if ($c >= 0) { |
125
|
7 |
|
return $c; |
126
|
|
|
} |
127
|
|
|
|
128
|
|
|
return imagecolorallocatealpha($gd, $red, $green, $blue, $alpha); |
129
|
|
|
} |
130
|
|
|
|
131
|
6 |
|
protected function parseHexColor($hexCol) |
132
|
|
|
{ |
133
|
6 |
|
if (preg_match('/^\#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i', $hexCol, $c)) { |
134
|
6 |
|
return [hexdec($c[1]), hexdec($c[2]), hexdec($c[3])]; |
135
|
|
|
} else { |
136
|
|
|
throw new \InvalidArgumentException("invalid hex colour"); |
137
|
|
|
} |
138
|
|
|
} |
139
|
|
|
|
140
|
2 |
|
private function render32bit(IconImage $img, $gd) |
141
|
|
|
{ |
142
|
|
|
// 32 bits: 4 bytes per pixel [ B | G | R | ALPHA ]. |
143
|
2 |
|
$offset = 0; |
144
|
2 |
|
$binary = $img->bmpData; |
145
|
|
|
|
146
|
2 |
|
for ($i = $img->height - 1; $i >= 0; --$i) { |
147
|
2 |
View Code Duplication |
for ($j = 0; $j < $img->width; ++$j) { |
|
|
|
|
148
|
|
|
//we translate the BGRA to aRGB ourselves, which is twice as fast |
149
|
|
|
//as calling imagecolorallocatealpha |
150
|
2 |
|
$alpha7 = ((~ord($binary[$offset + 3])) & 0xff) >> 1; |
151
|
2 |
|
if ($alpha7 < 127) { |
152
|
2 |
|
$col = ($alpha7 << 24) | |
153
|
2 |
|
(ord($binary[$offset + 2]) << 16) | |
154
|
2 |
|
(ord($binary[$offset + 1]) << 8) | |
155
|
2 |
|
(ord($binary[$offset])); |
156
|
2 |
|
imagesetpixel($gd, $j, $i, $col); |
157
|
2 |
|
} |
158
|
2 |
|
$offset += 4; |
159
|
2 |
|
} |
160
|
2 |
|
} |
161
|
2 |
|
} |
162
|
|
|
|
163
|
1 |
|
private function render24bit(IconImage $img, $gd) |
164
|
|
|
{ |
165
|
|
|
// 24 bits: 3 bytes per pixel [ B | G | R ]. |
166
|
|
|
|
167
|
1 |
|
$maskBits = $this->buildMaskBits($img); |
168
|
|
|
|
169
|
1 |
|
$binary = $img->bmpData; |
170
|
1 |
|
$offset = 0; |
171
|
1 |
|
$maskpos = 0; |
172
|
|
|
|
173
|
1 |
|
for ($i = $img->height - 1; $i >= 0; --$i) { |
174
|
1 |
View Code Duplication |
for ($j = 0; $j < $img->width; ++$j) { |
|
|
|
|
175
|
1 |
|
if ($maskBits[$maskpos] == 0) { |
176
|
|
|
//translate BGR to RGB |
177
|
1 |
|
$col = (ord($binary[$offset + 2]) << 16) | |
178
|
1 |
|
(ord($binary[$offset + 1]) << 8) | |
179
|
1 |
|
(ord($binary[$offset])); |
180
|
1 |
|
imagesetpixel($gd, $j, $i, $col); |
181
|
1 |
|
} |
182
|
1 |
|
$offset += 3; |
183
|
1 |
|
$maskpos++; |
184
|
1 |
|
} |
185
|
1 |
|
} |
186
|
1 |
|
} |
187
|
|
|
|
188
|
4 |
|
private function buildMaskBits(IconImage $img) |
189
|
|
|
{ |
190
|
4 |
|
$width = $img->width; |
191
|
4 |
|
if (($width % 32) > 0) { |
192
|
|
|
$width += (32 - ($img->width % 32)); |
193
|
|
|
} |
194
|
4 |
|
$offset = $img->width * $img->height * $img->bitCount / 8; |
195
|
4 |
|
$total_bytes = ($width * $img->height) / 8; |
196
|
4 |
|
$maskBits = ''; |
197
|
4 |
|
$bytes = 0; |
198
|
4 |
|
$bytes_per_line = ($img->width / 8); |
199
|
4 |
|
$bytes_to_remove = (($width - $img->width) / 8); |
200
|
4 |
View Code Duplication |
for ($i = 0; $i < $total_bytes; ++$i) { |
|
|
|
|
201
|
4 |
|
$maskBits .= str_pad(decbin(ord($img->bmpData[$offset + $i])), 8, '0', STR_PAD_LEFT); |
202
|
4 |
|
++$bytes; |
203
|
4 |
|
if ($bytes == $bytes_per_line) { |
204
|
4 |
|
$i += $bytes_to_remove; |
205
|
4 |
|
$bytes = 0; |
206
|
4 |
|
} |
207
|
4 |
|
} |
208
|
4 |
|
return $maskBits; |
209
|
|
|
} |
210
|
|
|
|
211
|
1 |
|
private function render8bit(IconImage $img, $gd) |
212
|
|
|
{ |
213
|
|
|
// 8 bits: 1 byte per pixel [ COLOR INDEX ]. |
214
|
1 |
|
$palette = $this->buildPalette($img, $gd); |
215
|
1 |
|
$maskBits = $this->buildMaskBits($img); |
216
|
|
|
|
217
|
1 |
|
$offset = 0; |
218
|
1 |
View Code Duplication |
for ($i = $img->height - 1; $i >= 0; --$i) { |
|
|
|
|
219
|
1 |
|
for ($j = 0; $j < $img->width; ++$j) { |
220
|
1 |
|
if ($maskBits[$offset] == 0) { |
221
|
1 |
|
$color = ord($img->bmpData[$offset]); |
222
|
1 |
|
imagesetpixel($gd, $j, $i, $palette[$color]); |
223
|
1 |
|
} |
224
|
1 |
|
++$offset; |
225
|
1 |
|
} |
226
|
1 |
|
} |
227
|
1 |
|
} |
228
|
|
|
|
229
|
3 |
|
private function buildPalette(IconImage $img, $gd) |
230
|
|
|
{ |
231
|
3 |
|
$palette = []; |
232
|
3 |
|
if ($img->bitCount != 24) { |
233
|
3 |
|
$palette = []; |
234
|
3 |
|
for ($i = 0; $i < $img->colorCount; ++$i) { |
235
|
3 |
|
$palette[$i] = $this->allocateColor( |
236
|
3 |
|
$gd, |
237
|
3 |
|
$img->palette[$i]['red'], |
238
|
3 |
|
$img->palette[$i]['green'], |
239
|
3 |
|
$img->palette[$i]['blue'], |
240
|
3 |
|
round($img->palette[$i]['reserved'] / 255 * 127) |
241
|
3 |
|
); |
242
|
3 |
|
} |
243
|
3 |
|
} |
244
|
3 |
|
return $palette; |
245
|
|
|
} |
246
|
|
|
|
247
|
1 |
|
private function render4bit(IconImage $img, $gd) |
248
|
|
|
{ |
249
|
|
|
//4 bits: half byte/nibble per pixel [ COLOR INDEX ]. |
250
|
1 |
|
$palette = $this->buildPalette($img, $gd); |
251
|
1 |
|
$maskBits = $this->buildMaskBits($img); |
252
|
|
|
|
253
|
1 |
|
$offset = 0; |
254
|
1 |
|
$maskoffset = 0; |
255
|
1 |
|
for ($i = $img->height - 1; $i >= 0; --$i) { |
256
|
1 |
|
for ($j = 0; $j < $img->width; $j += 2) { |
257
|
1 |
|
$colorByte = ord($img->bmpData[$offset]); |
258
|
1 |
|
$lowNibble = $colorByte & 0x0f; |
259
|
1 |
|
$highNibble = ($colorByte & 0xf0) >> 4; |
260
|
|
|
|
261
|
1 |
|
if ($maskBits[$maskoffset++] == 0) { |
262
|
1 |
|
imagesetpixel($gd, $j, $i, $palette[$highNibble]); |
263
|
1 |
|
} |
264
|
|
|
|
265
|
1 |
View Code Duplication |
if ($maskBits[$maskoffset++] == 0) { |
|
|
|
|
266
|
1 |
|
imagesetpixel($gd, $j + 1, $i, $palette[$lowNibble]); |
267
|
1 |
|
} |
268
|
1 |
|
$offset++; |
269
|
1 |
|
} |
270
|
1 |
|
} |
271
|
1 |
|
} |
272
|
|
|
|
273
|
1 |
|
private function render1bit(IconImage $img, $gd) |
274
|
|
|
{ |
275
|
|
|
//1 bit: 1 bit per pixel (2 colors, usually black&white) [ COLOR INDEX ]. |
276
|
1 |
|
$palette = $this->buildPalette($img, $gd); |
277
|
1 |
|
$maskBits = $this->buildMaskBits($img); |
278
|
|
|
|
279
|
|
|
|
280
|
1 |
|
$colorbits = ''; |
281
|
1 |
|
$total = strlen($img->bmpData); |
282
|
1 |
View Code Duplication |
for ($i = 0; $i < $total; ++$i) { |
|
|
|
|
283
|
1 |
|
$colorbits .= str_pad(decbin(ord($img->bmpData[$i])), 8, '0', STR_PAD_LEFT); |
284
|
1 |
|
} |
285
|
|
|
|
286
|
1 |
|
$offset = 0; |
287
|
1 |
View Code Duplication |
for ($i = $img->height - 1; $i >= 0; --$i) { |
|
|
|
|
288
|
1 |
|
for ($j = 0; $j < $img->width; ++$j) { |
289
|
1 |
|
if ($maskBits[$offset] == 0) { |
290
|
1 |
|
imagesetpixel($gd, $j, $i, $palette[$colorbits[$offset]]); |
291
|
1 |
|
} |
292
|
1 |
|
++$offset; |
293
|
1 |
|
} |
294
|
1 |
|
} |
295
|
1 |
|
} |
296
|
|
|
} |
297
|
|
|
|
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.