Passed
Push — master ( 9aa048...898b5e )
by Mattia
04:05
created

IsometricAvatar::renderFullSize()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 84
Code Lines 48

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 1
eloc 48
c 3
b 0
f 0
nc 1
nop 0
dl 0
loc 84
rs 9.1344

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Minepic\Image;
6
7
use Minepic\Helpers\Storage\Files\IsometricsStorage;
8
use Minepic\Helpers\Storage\Files\SkinsStorage;
9
use Minepic\Image\Exceptions\SkinNotFountException;
10
use Minepic\Image\Sections\Avatar;
11
12
/**
13
 * Class IsometricAvatar.
14
 */
15
class IsometricAvatar
16
{
17
    /**
18
     * Cosine PI/6.
19
     */
20
    private const COSINE_PI_6 = M_SQRT3 / 2;
21
22
    /**
23
     * Base size (all requests must be <=).
24
     */
25
    private const HEAD_BASE_SIZE = 252;
26
27
    /**
28
     * Margin (in pixels) in the final image.
29
     */
30
    private const HEAD_MARGIN = 4;
31
32
    /**
33
     * Maximum size for resize operation.
34
     */
35
    private const MAX_SIZE = 512;
36
37
    /**
38
     * Minimum size for resize operation.
39
     */
40
    private const MIN_SIZE = 16;
41
42
    /**
43
     * User UUID.
44
     *
45
     * @var string
46
     */
47
    protected string $uuid = '';
48
49
    /**
50
     * Last time user data has been updated.
51
     *
52
     * @var int
53
     */
54
    protected int $lastUpdate = 0;
55
56
    /**
57
     * Flag for checking cache.
58
     *
59
     * @var bool
60
     */
61
    protected bool $checkCacheStatusFlag = true;
62
63
    /**
64
     * Skin Path.
65
     *
66
     * @var string
67
     */
68
    protected string $skinPath = '';
69
70
    /**
71
     * Skin Path.
72
     *
73
     * @var string
74
     */
75
    protected string $isometricPath = '';
76
77
    /**
78
     * @var \Imagick
79
     */
80
    protected \Imagick $head;
81
82
    /**
83
     * IsometricAvatar constructor.
84
     *
85
     * @param string $uuid       User UUID
86
     * @param int    $lastUpdate
87
     *
88
     * @throws SkinNotFountException
89
     */
90
    public function __construct(string $uuid, int $lastUpdate)
91
    {
92
        $this->uuid = $uuid;
93
        $this->lastUpdate = $lastUpdate;
94
95
        if (SkinsStorage::exists($uuid)) {
96
            $this->skinPath = SkinsStorage::getPath($uuid);
97
        } else {
98
            throw new SkinNotFountException();
99
        }
100
101
        if (IsometricsStorage::exists($uuid)) {
102
            $this->isometricPath = IsometricsStorage::getPath($uuid);
103
        }
104
    }
105
106
    /**
107
     * __toString().
108
     */
109
    public function __toString(): string
110
    {
111
        return (string) $this->head;
112
    }
113
114
    /**
115
     * Get ImagickPixel transparent object.
116
     *
117
     * @return \ImagickPixel
118
     */
119
    final protected function getImagickPixelTransparent(): \ImagickPixel
120
    {
121
        return new \ImagickPixel('transparent');
122
    }
123
124
    /**
125
     * Point for face section.
126
     *
127
     * @param int $size
128
     *
129
     * @return array
130
     */
131
    private function getFrontPoints($size = self::HEAD_BASE_SIZE): array
132
    {
133
        $cosine_result = \round(self::COSINE_PI_6 * $size);
134
        $half_size = \round($size / 2);
135
136
        return [
137
            0, 0, 0, 0,
138
            0, $size, 0, $size,
139
            $size, 0, -$cosine_result, $half_size,
140
        ];
141
    }
142
143
    /**
144
     * Points for top section.
145
     *
146
     * @param int $size
147
     *
148
     * @return array
149
     */
150
    private function getTopPoints($size = self::HEAD_BASE_SIZE): array
151
    {
152
        $cosine_result = \round(self::COSINE_PI_6 * $size);
153
        $half_size = \round($size / 2);
154
155
        return [
156
            0, $size, 0, 0,
157
            0, 0, -$cosine_result, -($half_size),
158
            $size, $size, $cosine_result, -($half_size),
159
        ];
160
    }
161
162
    /**
163
     * Points for right section.
164
     *
165
     * @param int $size
166
     *
167
     * @return array
168
     */
169
    private function getRightPoints($size = self::HEAD_BASE_SIZE): array
170
    {
171
        $cosine_result = \round(self::COSINE_PI_6 * $size);
172
        $half_size = \round($size / 2);
173
174
        return [
175
            $size, 0, 0, 0,
176
            0, 0, -($cosine_result), -($half_size),
177
            $size, $size, 0, $size,
178
        ];
179
    }
180
181
    /**
182
     * Render Isometric from avatar sections.
183
     *
184
     * @throws \Throwable
185
     */
186
    protected function renderFullSize(): void
187
    {
188
        // Create Avatar Object
189
        $avatar = new Avatar($this->skinPath);
190
191
        // Face
192
        $avatar->render(self::HEAD_BASE_SIZE, ImageSection::FRONT);
193
194
        $face = new \Imagick();
195
        $face->readImageBlob((string) $avatar);
196
        $face->brightnessContrastImage(8, 8);
197
        $face->setImageVirtualPixelMethod(\Imagick::VIRTUALPIXELMETHOD_TRANSPARENT);
198
        $face->setBackgroundColor(
199
            $this->getImagickPixelTransparent()
200
        );
201
202
        $face->distortImage(
203
            \Imagick::DISTORTION_AFFINE,
204
            $this->getFrontPoints(),
205
            true
206
        );
207
208
        // Top
209
        $avatar->render(self::HEAD_BASE_SIZE, ImageSection::TOP);
210
211
        $top = new \Imagick();
212
        $top->readImageBlob((string) $avatar);
213
        $top->brightnessContrastImage(6, 6);
214
        $top->setImageVirtualPixelMethod(\Imagick::VIRTUALPIXELMETHOD_TRANSPARENT);
215
        $top->setBackgroundColor(
216
            $this->getImagickPixelTransparent()
217
        );
218
219
        $top->distortImage(
220
            \Imagick::DISTORTION_AFFINE,
221
            $this->getTopPoints(),
222
            true
223
        );
224
225
        // Right
226
        $avatar->render(self::HEAD_BASE_SIZE, ImageSection::RIGHT);
227
228
        $right = new \Imagick();
229
        $right->readImageBlob((string) $avatar);
230
        $right->brightnessContrastImage(4, 4);
231
232
        $right->setImageVirtualPixelMethod(\Imagick::VIRTUALPIXELMETHOD_TRANSPARENT);
233
        $right->setBackgroundColor(
234
            $this->getImagickPixelTransparent()
235
        );
236
237
        $right->distortImage(
238
            \Imagick::DISTORTION_AFFINE,
239
            $this->getRightPoints(),
240
            true
241
        );
242
243
        // Head image
244
        $doubleAvatarSize = self::HEAD_BASE_SIZE * 2;
245
        $finalImageSize = $doubleAvatarSize + (self::HEAD_MARGIN * 2);
246
247
        $this->head = new \Imagick();
248
        $this->head->newImage($finalImageSize, $finalImageSize, $this->getImagickPixelTransparent());
249
250
        // This is weird, but it works
251
        $faceX = ((int) \round(($doubleAvatarSize / 2))) - 2 + self::HEAD_MARGIN;
252
        $faceY = $rightY = ((int) \round($doubleAvatarSize / 4)) - 1 + self::HEAD_MARGIN;
253
        $topX = $rightX = ((int) \round($doubleAvatarSize / 16)) + self::HEAD_MARGIN;
254
        $topY = -1 + self::HEAD_MARGIN;
255
256
        // Add Face Section
257
        $this->head->compositeimage($face->getimage(), \Imagick::COMPOSITE_PLUS, $faceX, $faceY);
258
259
        // Add Top Section
260
        $this->head->compositeimage($top->getimage(), \Imagick::COMPOSITE_PLUS, $topX, $topY);
261
262
        // Add Right Section
263
        $this->head->compositeimage($right->getimage(), \Imagick::COMPOSITE_PLUS, $rightX, $rightY);
264
265
        // Set format to PNG
266
        $this->head->setImageFormat('png');
267
268
        $this->isometricPath = IsometricsStorage::getPath($this->uuid);
269
        $this->head->writeImage($this->isometricPath);
270
    }
271
272
    /**
273
     * Create $head Imagick Object from previously rendered head.
274
     *
275
     * @throws \ImagickException
276
     */
277
    protected function createFromFile(): void
278
    {
279
        $this->head = new \Imagick($this->isometricPath);
280
    }
281
282
    /**
283
     * Check cached file.
284
     */
285
    protected function verifyCachedFile(): bool
286
    {
287
        if (!$this->checkCacheStatusFlag) {
288
            return true;
289
        }
290
291
        if (!$this->isometricPath || (\filemtime($this->isometricPath) <= $this->lastUpdate)) {
292
            return false;
293
        }
294
295
        return true;
296
    }
297
298
    /**
299
     * Render image resized.
300
     *
301
     * @param $size
302
     *
303
     * @throws \Throwable
304
     */
305
    public function render($size): void
306
    {
307
        if ($size < self::MIN_SIZE || $size > self::MAX_SIZE) {
308
            $size = 256;
309
        }
310
311
        if (!$this->verifyCachedFile()) {
312
            $this->renderFullSize();
313
        } else {
314
            $this->createFromFile();
315
        }
316
317
        if ($size !== self::MAX_SIZE) {
318
            $this->head->resizeImage($size, $size, \Imagick::FILTER_LANCZOS2, 0.9);
319
        }
320
    }
321
}
322