Passed
Push — analysis-b0m7na ( 631964 )
by Arnaud
16:06 queued 11:14
created

Image::getSvgAttributes()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 5
c 0
b 0
f 0
nc 3
nop 1
dl 0
loc 11
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of Cecil.
7
 *
8
 * Copyright (c) Arnaud Ligny <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Cecil\Assets;
15
16
use Cecil\Exception\RuntimeException;
17
use Intervention\Image\ImageManagerStatic as ImageManager;
18
19
class Image
20
{
21
    /**
22
     * Resize an image Asset.
23
     *
24
     * @throws RuntimeException
25
     */
26
    public static function resize(Asset $asset, int $width, int $quality): string
27
    {
28
        try {
29
            // is image Asset?
30
            if ($asset['type'] !== 'image') {
31
                throw new RuntimeException(sprintf('Not an image.'));
32
            }
33
            // is GD is installed
34
            if (!\extension_loaded('gd')) {
35
                throw new RuntimeException('GD extension is required.');
36
            }
37
            // creates image object from source
38
            $image = ImageManager::make($asset['content_source']);
39
            // resizes to $width with constraint the aspect-ratio and unwanted upsizing
40
            $image->resize($width, null, function (\Intervention\Image\Constraint $constraint) {
41
                $constraint->aspectRatio();
42
                $constraint->upsize();
43
            });
44
            // interlaces (PNG) or progressives (JPEG) image
45
            $image->interlace();
46
            // save image in extension format and given quality
47
            $imageAsString = (string) $image->encode($asset['ext'], $quality);
48
            // destroy image object
49
            $image->destroy();
50
51
            return $imageAsString;
52
        } catch (\Exception $e) {
53
            throw new RuntimeException(sprintf('Not able to resize "%s": %s', $asset['path'], $e->getMessage()));
54
        }
55
    }
56
57
    /**
58
     * Converts an image Asset to the target format.
59
     *
60
     * @throws RuntimeException
61
     */
62
    public static function convert(Asset $asset, string $format, int $quality): string
63
    {
64
        try {
65
            if ($asset['type'] !== 'image') {
66
                throw new RuntimeException(sprintf('Not an image.'));
67
            }
68
            $image = ImageManager::make($asset['content']);
69
            $imageAsString = (string) $image->encode($format, $quality);
70
            $image->destroy();
71
72
            return $imageAsString;
73
        } catch (\Exception $e) {
74
            throw new RuntimeException(sprintf('Not able to resize "%s": %s', $asset['path'], $e->getMessage()));
75
        }
76
    }
77
78
    /**
79
     * Returns the Data URL (encoded in Base64).
80
     *
81
     * @throws RuntimeException
82
     */
83
    public static function getDataUrl(Asset $asset, int $quality): string
84
    {
85
        try {
86
            if ($asset['type'] != 'image' || self::isSVG($asset)) {
87
                throw new RuntimeException(sprintf('Not an image.'));
88
            }
89
90
            return (string) ImageManager::make($asset['content'])->encode('data-url', $quality);
91
        } catch (\Exception $e) {
92
            throw new RuntimeException(sprintf('Can\'t get Data URL of "%s": %s', $asset['path'], $e->getMessage()));
93
        }
94
    }
95
96
    /**
97
     * Returns the dominant hexadecimal color of an image asset.
98
     *
99
     * @throws RuntimeException
100
     */
101
    public static function getDominantColor(Asset $asset): string
102
    {
103
        try {
104
            if ($asset['type'] != 'image' || self::isSVG($asset)) {
105
                throw new RuntimeException(sprintf('Not an image.'));
106
            }
107
108
            $assetColor = clone $asset;
109
            $assetColor = $assetColor->resize(100);
110
            $image = ImageManager::make($assetColor['content']);
111
            $color = $image->limitColors(1)->pickColor(0, 0, 'hex');
112
            $image->destroy();
113
114
            return $color;
115
        } catch (\Exception $e) {
116
            throw new RuntimeException(sprintf('Can\'t get Data URL of "%s": %s', $asset['path'], $e->getMessage()));
117
        }
118
    }
119
120
    /**
121
     * Returns a Low Quality Image Placeholder (LQIP) as data URL.
122
     *
123
     * @throws RuntimeException
124
     */
125
    public static function getLqip(Asset $asset): string
126
    {
127
        if ($asset['type'] !== 'image') {
128
            throw new RuntimeException(sprintf('can\'t create LQIP of "%s": it\'s not an image.', $asset['path']));
129
        }
130
131
        $assetLqip = clone $asset;
132
        $assetLqip = $assetLqip->resize(100);
133
134
        return (string) ImageManager::make($assetLqip['content'])->blur(50)->encode('data-url');
135
    }
136
137
    /**
138
     * Build the `srcset` attribute for responsive images.
139
     * e.g.: `srcset="/img-480.jpg 480w, /img-800.jpg 800w"`.
140
     *
141
     * @throws RuntimeException
142
     */
143
    public static function buildSrcset(Asset $asset, array $widths): string
144
    {
145
        if ($asset['type'] !== 'image') {
146
            throw new RuntimeException(sprintf('can\'t build "srcset" of "%s": it\'s not an image file.', $asset['path']));
147
        }
148
149
        $srcset = '';
150
        $widthMax = 0;
151
        foreach ($widths as $width) {
152
            if ($asset['width'] < $width) {
153
                break;
154
            }
155
            $img = $asset->resize($width);
156
            $srcset .= sprintf('%s %sw, ', (string) $img, $width);
157
            $widthMax = $width;
158
        }
159
        // adds source image
160
        if (!empty($srcset) && ($asset['width'] < max($widths) && $asset['width'] != $widthMax)) {
161
            $srcset .= sprintf('%s %sw', (string) $asset, $asset['width']);
162
        }
163
164
        return rtrim($srcset, ', ');
165
    }
166
167
    /**
168
     * Returns the value of the "sizes" attribute corresponding to the configured class.
169
     */
170
    public static function getSizes(string $class, array $sizes = []): string
171
    {
172
        $result = '';
173
        $classArray = explode(' ', $class);
174
        foreach ($classArray as $class) {
175
            if (\array_key_exists($class, $sizes)) {
176
                $result = $sizes[$class] . ', ';
177
            }
178
        }
179
        if (!empty($result)) {
180
            return trim($result, ', ');
181
        }
182
183
        return $sizes['default'] ?? '100vw';
184
    }
185
186
    /**
187
     * Checks if an asset is an animated GIF.
188
     */
189
    public static function isAnimatedGif(Asset $asset): bool
190
    {
191
        // an animated GIF contains multiple "frames", with each frame having a header made up of:
192
        // 1. a static 4-byte sequence (\x00\x21\xF9\x04)
193
        // 2. 4 variable bytes
194
        // 3. a static 2-byte sequence (\x00\x2C)
195
        $count = preg_match_all('#\x00\x21\xF9\x04.{4}\x00[\x2C\x21]#s', (string) $asset['content_source']);
196
197
        return $count > 1;
198
    }
199
200
    /**
201
     * Returns true if asset is a SVG.
202
     */
203
    public static function isSVG(Asset $asset): bool
204
    {
205
        return \in_array($asset['subtype'], ['image/svg', 'image/svg+xml']) || $asset['ext'] == 'svg';
206
    }
207
208
    /**
209
     * Returns SVG attributes.
210
     *
211
     * @return \SimpleXMLElement|false
212
     */
213
    public static function getSvgAttributes(Asset $asset)
214
    {
215
        if (!self::isSVG($asset)) {
216
            return false;
217
        }
218
219
        if (false === $xml = simplexml_load_string($asset['content_source'])) {
0 ignored issues
show
Bug introduced by
It seems like $asset['content_source'] can also be of type null; however, parameter $data of simplexml_load_string() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

219
        if (false === $xml = simplexml_load_string(/** @scrutinizer ignore-type */ $asset['content_source'])) {
Loading history...
220
            return false;
221
        }
222
223
        return $xml->attributes();
224
    }
225
}
226