Completed
Pull Request — master (#4)
by Paul
02:03
created

GdRenderer   B

Complexity

Total Complexity 52

Size/Duplication

Total Lines 286
Duplicated Lines 22.38 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 98.21%

Importance

Changes 0
Metric Value
wmc 52
lcom 1
cbo 1
dl 64
loc 286
ccs 165
cts 168
cp 0.9821
rs 7.9487
c 0
b 0
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
A render() 0 16 4
B initOptions() 0 8 5
A resize() 0 6 1
A renderPngImage() 0 19 2
C renderBmpImage() 0 39 7
A allocateColor() 9 9 2
A parseHexColor() 0 8 2
B render32bit() 13 22 4
B render24bit() 11 24 4
B buildMaskBits() 8 22 4
A render8bit() 9 17 4
A buildPalette() 0 17 3
B render4bit() 3 25 5
B render1bit() 11 23 5

How to fix   Duplicated Code    Complexity   

Duplicated Code

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 Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like GdRenderer 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 GdRenderer, and based on these observations, apply Extract Interface, too.

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