Completed
Push — master ( 312aa1...eb49de )
by Paul
9s
created

GdRenderer   B

Complexity

Total Complexity 52

Size/Duplication

Total Lines 287
Duplicated Lines 22.3 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 97.97%

Importance

Changes 0
Metric Value
wmc 52
lcom 1
cbo 1
dl 64
loc 287
ccs 193
cts 197
cp 0.9797
rs 7.9487
c 0
b 0
f 0

14 Methods

Rating   Name   Duplication   Size   Complexity  
B initOptions() 0 8 5
A render() 0 16 4
A resize() 0 7 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 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)
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...
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) {
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...
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) {
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...
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) {
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...
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) {
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...
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) {
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...
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) {
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...
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) {
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...
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