Completed
Push — master ( eb49de...2435ec )
by Paul
8s
created

GdRenderer::buildMaskBits()   B

Complexity

Conditions 4
Paths 6

Size

Total Lines 22
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 22
ccs 20
cts 20
cp 1
rs 8.9197
c 0
b 0
f 0
cc 4
eloc 17
nc 6
nop 1
crap 4
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 9
    public function render(IconImage $img, array $opts = null)
13
    {
14 9
        $opts = $this->initOptions($img, $opts);
15
16 9
        if ($img->isPng()) {
17 2
            $gd = $this->renderPngImage($img, $opts['background']);
18 1
        } else {
19 7
            $gd = $this->renderBmpImage($img, $opts['background']);
20
        }
21
22 8
        if ((imagesx($gd) != $opts['w']) && (imagesy($gd) != $opts['h'])) {
23 1
            $gd = $this->resize($gd, $opts['w'], $opts['h']);
24 1
        }
25
26 8
        return $gd;
27
    }
28
29 9
    protected function initOptions(IconImage $img, $opts)
30
    {
31 9
        $opts = is_array($opts) ? $opts : [];
32 9
        $opts['w'] = isset($opts['w']) ? $opts['w'] : $img->width;
33 9
        $opts['h'] = isset($opts['h']) ? $opts['h'] : $img->height;
34 9
        $opts['background'] = isset($opts['background']) ? $opts['background'] : null;
35 9
        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 2
    protected function renderPngImage(IconImage $img, $hexBackgroundColor)
47
    {
48 2
        $im = imagecreatefromstring($img->pngData);
49 2
        imagesavealpha($im, true);
50
51 2
        if (!is_null($hexBackgroundColor)) {
52 2
            $w = $img->width;
53 2
            $h = $img->height;
54
55 2
            $gd = imagecreatetruecolor($w, $h);
56 2
            $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 7
    protected function renderBmpImage(IconImage $img, $hexBackgroundColor = null)
67
    {
68
        // create image filled with desired background color
69 7
        $w = $img->width;
70 7
        $h = $img->height;
71 7
        $gd = imagecreatetruecolor($w, $h);
72
73 7
        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 6
            $col = $this->parseHexColor($hexBackgroundColor);
80 6
            $colVal = $this->allocateColor($gd, $col[0], $col[1], $col[2]);
81 6
            imagefilledrectangle($gd, 0, 0, $w, $h, $colVal);
82
        }
83
84
        // now paint pixels based on bit count
85 7
        switch ($img->bitCount) {
86 7
            case 32:
87 2
                $this->render32bit($img, $gd);
88 2
                break;
89 5
            case 24:
90 1
                $this->render24bit($img, $gd);
91 1
                break;
92 4
            case 8:
93 2
                $this->render8bit($img, $gd);
94 2
                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 7
        }
102
103 7
        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 8
    private function allocateColor($gd, $red, $green, $blue, $alpha = 0)
122
    {
123 8
        $c = imagecolorexactalpha($gd, $red, $green, $blue, $alpha);
124 8
        if ($c >= 0) {
125 8
            return $c;
126
        }
127
128
        //we don't use this for calculating 32bit color values
129
        //@codeCoverageIgnoreStart
130
        return imagecolorallocatealpha($gd, $red, $green, $blue, $alpha);
131
        //@codeCoverageIgnoreEnd
132
    }
133
134 8
    protected function parseHexColor($hexCol)
135
    {
136 8
        if (preg_match('/^\#?([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i', $hexCol, $c)) {
137 7
            return [hexdec($c[1]), hexdec($c[2]), hexdec($c[3])];
138
        } else {
139 1
            throw new \InvalidArgumentException("invalid hex colour");
140
        }
141
    }
142
143 2
    private function render32bit(IconImage $img, $gd)
144
    {
145
        // 32 bits: 4 bytes per pixel [ B | G | R | ALPHA ].
146 2
        $offset = 0;
147 2
        $binary = $img->bmpData;
148
149 2
        for ($i = $img->height - 1; $i >= 0; --$i) {
150 2
            for ($j = 0; $j < $img->width; ++$j) {
151
                //we translate the BGRA to aRGB ourselves, which is twice as fast
152
                //as calling imagecolorallocatealpha
153 2
                $alpha7 = ((~ord($binary[$offset + 3])) & 0xff) >> 1;
154 2 View Code Duplication
                if ($alpha7 < 127) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
155 2
                    $col = ($alpha7 << 24) |
156 2
                        (ord($binary[$offset + 2]) << 16) |
157 2
                        (ord($binary[$offset + 1]) << 8) |
158 2
                        (ord($binary[$offset]));
159 2
                    imagesetpixel($gd, $j, $i, $col);
160 2
                }
161 2
                $offset += 4;
162 2
            }
163 2
        }
164 2
    }
165
166 1
    private function render24bit(IconImage $img, $gd)
167
    {
168
        // 24 bits: 3 bytes per pixel [ B | G | R ].
169
170 1
        $maskBits = $this->buildMaskBits($img);
171
172 1
        $binary = $img->bmpData;
173 1
        $offset = 0;
174 1
        $maskpos = 0;
175
176 1
        for ($i = $img->height - 1; $i >= 0; --$i) {
177 1
            for ($j = 0; $j < $img->width; ++$j) {
178 1 View Code Duplication
                if ($maskBits[$maskpos] == 0) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
179
                    //translate BGR to RGB
180 1
                    $col = (ord($binary[$offset + 2]) << 16) |
181 1
                        (ord($binary[$offset + 1]) << 8) |
182 1
                        (ord($binary[$offset]));
183 1
                    imagesetpixel($gd, $j, $i, $col);
184 1
                }
185 1
                $offset += 3;
186 1
                $maskpos++;
187 1
            }
188 1
        }
189 1
    }
190
191 5
    private function buildMaskBits(IconImage $img)
192
    {
193 5
        $width = $img->width;
194 5
        if (($width % 32) > 0) {
195 1
            $width += (32 - ($img->width % 32));
196 1
        }
197 5
        $offset = $img->width * $img->height * $img->bitCount / 8;
198 5
        $total_bytes = ($width * $img->height) / 8;
199 5
        $maskBits = '';
200 5
        $bytes = 0;
201 5
        $bytes_per_line = ($img->width / 8);
202 5
        $bytes_to_remove = (($width - $img->width) / 8);
203 5
        for ($i = 0; $i < $total_bytes; ++$i) {
204 5
            $maskBits .= str_pad(decbin(ord($img->bmpData[$offset + $i])), 8, '0', STR_PAD_LEFT);
205 5
            ++$bytes;
206 5
            if ($bytes == $bytes_per_line) {
207 5
                $i += $bytes_to_remove;
208 5
                $bytes = 0;
209 5
            }
210 5
        }
211 5
        return $maskBits;
212
    }
213
214 2
    private function render8bit(IconImage $img, $gd)
215
    {
216
        // 8 bits: 1 byte per pixel [ COLOR INDEX ].
217 2
        $palette = $this->buildPalette($img, $gd);
218 2
        $maskBits = $this->buildMaskBits($img);
219
220 2
        $offset = 0;
221 2 View Code Duplication
        for ($i = $img->height - 1; $i >= 0; --$i) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
222 2
            for ($j = 0; $j < $img->width; ++$j) {
223 2
                if ($maskBits[$offset] == 0) {
224 2
                    $color = ord($img->bmpData[$offset]);
225 2
                    imagesetpixel($gd, $j, $i, $palette[$color]);
226 2
                }
227 2
                ++$offset;
228 2
            }
229 2
        }
230 2
    }
231
232 4
    private function buildPalette(IconImage $img, $gd)
233
    {
234 4
        $palette = [];
235 4
        if ($img->bitCount != 24) {
236 4
            $palette = [];
237 4
            for ($i = 0; $i < $img->colorCount; ++$i) {
238 4
                $palette[$i] = $this->allocateColor(
239 4
                    $gd,
240 4
                    $img->palette[$i]['red'],
241 4
                    $img->palette[$i]['green'],
242 4
                    $img->palette[$i]['blue'],
243 4
                    round($img->palette[$i]['reserved'] / 255 * 127)
244 4
                );
245 4
            }
246 4
        }
247 4
        return $palette;
248
    }
249
250 1
    private function render4bit(IconImage $img, $gd)
251
    {
252
        //4 bits: half byte/nibble per pixel [ COLOR INDEX ].
253 1
        $palette = $this->buildPalette($img, $gd);
254 1
        $maskBits = $this->buildMaskBits($img);
255
256 1
        $offset = 0;
257 1
        $maskoffset = 0;
258 1
        for ($i = $img->height - 1; $i >= 0; --$i) {
259 1
            for ($j = 0; $j < $img->width; $j += 2) {
260 1
                $colorByte = ord($img->bmpData[$offset]);
261 1
                $lowNibble = $colorByte & 0x0f;
262 1
                $highNibble = ($colorByte & 0xf0) >> 4;
263
264 1
                if ($maskBits[$maskoffset++] == 0) {
265 1
                    imagesetpixel($gd, $j, $i, $palette[$highNibble]);
266 1
                }
267
268 1
                if ($maskBits[$maskoffset++] == 0) {
269 1
                    imagesetpixel($gd, $j + 1, $i, $palette[$lowNibble]);
270 1
                }
271 1
                $offset++;
272 1
            }
273 1
        }
274 1
    }
275
276 1
    private function render1bit(IconImage $img, $gd)
277
    {
278
        //1 bit: 1 bit per pixel (2 colors, usually black&white) [ COLOR INDEX ].
279 1
        $palette = $this->buildPalette($img, $gd);
280 1
        $maskBits = $this->buildMaskBits($img);
281
282
283 1
        $colorbits = '';
284 1
        $total = strlen($img->bmpData);
285 1
        for ($i = 0; $i < $total; ++$i) {
286 1
            $colorbits .= str_pad(decbin(ord($img->bmpData[$i])), 8, '0', STR_PAD_LEFT);
287 1
        }
288
289 1
        $offset = 0;
290 1 View Code Duplication
        for ($i = $img->height - 1; $i >= 0; --$i) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
291 1
            for ($j = 0; $j < $img->width; ++$j) {
292 1
                if ($maskBits[$offset] == 0) {
293 1
                    imagesetpixel($gd, $j, $i, $palette[$colorbits[$offset]]);
294 1
                }
295 1
                ++$offset;
296 1
            }
297 1
        }
298 1
    }
299
}
300