Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
85.85% covered (warning)
85.85%
91 / 106
78.26% covered (warning)
78.26%
18 / 23
CRAP
0.00% covered (danger)
0.00%
0 / 1
MetaData
85.85% covered (warning)
85.85%
91 / 106
78.26% covered (warning)
78.26%
18 / 23
59.66
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
25 / 25
100.00% covered (success)
100.00%
1 / 1
2
 getResolution
40.00% covered (danger)
40.00%
4 / 10
0.00% covered (danger)
0.00%
0 / 1
4.94
 getResolutionHeight
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getResolutionWidth
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getFormat
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDepth
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getColorspace
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getGeometry
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getHeight
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getWidth
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 listRaw
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
4
 parseFilename
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 parseFileformat
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 parseGeometry
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
4
 parseColorspace
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 parseDepth
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 parseResolution
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 parseVerbose
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 parse
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 parseUnits
62.50% covered (warning)
62.50%
10 / 16
0.00% covered (danger)
0.00%
0 / 1
7.90
 getUnit
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 getHash
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
3.04
 ppc2ppi
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3/**
4 * Karla ImageMagick wrapper library
5 *
6 * PHP Version 8.0<
7 *
8 * @category Utility
9 * @author   Johannes Skov Frandsen <jsf@greenoak.dk>
10 * @license  http://www.opensource.org/licenses/mit-license.php MIT
11 * @link     https://github.com/localgod/karla Karla
12 * @since    2012-04-05
13 */
14
15declare(strict_types=1);
16
17namespace Karla;
18
19/**
20 * Class for wrapping image metadata
21 *
22 * @category Utility
23 * @author   Johannes Skov Frandsen <jsf@greenoak.dk>
24 * @license  http://www.opensource.org/licenses/mit-license.php MIT
25 * @link     https://github.com/localgod/karla Karla
26 */
27class MetaData extends \SplFileInfo
28{
29    /**
30     * Raw image info
31     *
32     * @var array<string>
33     */
34    private array $verboseImageinfo = [];
35
36    /**
37     * Raw image info
38     *
39     * @var array<string, string>
40     */
41    private array $imageinfo = [];
42
43    /**
44     * The image format.
45     *
46     * @var string
47     */
48    private string $format = '';
49
50    /**
51     * The image depth.
52     *
53     * @var int
54     */
55    private int $depth = 0;
56
57    /**
58     * The image colorspace.
59     *
60     * @var string
61     */
62    private string $colorspace = '';
63
64    /**
65     * The image geometry
66     *
67     * @var array<int>
68     */
69    private array $geometry = [];
70
71    /**
72     * The image resolution
73     *
74     * @var array<int>
75     */
76    private array $resolution = [];
77
78    /**
79     * The resolution unit is measured in Pixels Per Inch
80     *
81     * @var bool
82     */
83    private bool $isPpi = false;
84
85    /**
86     * The resolution unit is measured in Pixels Per Centimeter
87     *
88     * @var bool
89     */
90    private bool $isPpc = false;
91
92    /**
93     * Are we scanning in verbose output from identify
94     *
95     * @var bool
96     */
97    private bool $verbose = false;
98
99    /**
100     * Construct a new image file.
101     *
102     * @param string $imageinfo Image info as string
103     * @param bool $verbose Should input be parsed as verbose
104     */
105    public function __construct(string $imageinfo, bool $verbose = false)
106    {
107        $this->verbose = $verbose;
108        if ($this->verbose) {
109            $this->verboseImageinfo = explode("\n", $imageinfo);
110            $this->parseFileformat();
111            $this->parseGeometry();
112            $this->parseResolution();
113            $this->parseUnits();
114            $this->parseDepth();
115            $this->parseColorspace();
116            parent::__construct($this->parseFilename());
117        } else {
118            $info = explode(" ", $imageinfo);
119            $this->imageinfo['Image'] = $info[0];
120            $this->imageinfo['Format'] = $info[1];
121            $this->imageinfo['Geometry'] = $info[3];
122            $this->imageinfo['Depth'] = $info[4];
123            $this->imageinfo['Colorspace'] = $info[5];
124            $this->imageinfo['Resolution'] = 'x';
125            $this->imageinfo['Units'] = '';
126            $this->parseFileformat();
127            $this->parseGeometry();
128            $this->parseResolution();
129            $this->parseUnits();
130            $this->parseDepth();
131            $this->parseColorspace();
132            parent::__construct($this->parseFilename());
133        }
134    }
135
136    /**
137     * Get the image resolution.
138     * If the resolution is in ppc we convert it to ppi
139     *
140     * @return array<int>
141     */
142    private function getResolution(): array
143    {
144        if ($this->verbose) {
145            if ($this->isPpc) {
146                // Here we convert to ppi
147                $this->resolution = array(
148                    $this->ppc2ppi($this->resolution[0]),
149                    $this->ppc2ppi($this->resolution[1])
150                );
151                $this->isPpc = false;
152                $this->isPpi = true;
153            }
154        } else {
155            $this->resolution = array();
156        }
157
158        return $this->resolution;
159    }
160
161    /**
162     * Get the resolution height.
163     */
164    public function getResolutionHeight(): int|null
165    {
166        $res = $this->getResolution();
167        return isset($res[1]) ? (int) $res[1] : null;
168    }
169
170    /**
171     * Get the resolution width.
172     */
173    public function getResolutionWidth(): int|null
174    {
175        $res = $this->getResolution();
176
177        return isset($res[0]) ? (int) $res[0] : null;
178    }
179
180    /**
181     * Get file format
182     */
183    public function getFormat(): string
184    {
185        return $this->format;
186    }
187
188    /**
189     * Get image depth
190     */
191    public function getDepth(): int
192    {
193        return $this->depth;
194    }
195
196    /**
197     * Get colorspace
198     */
199    public function getColorspace(): string
200    {
201        return $this->colorspace;
202    }
203
204    /**
205     * Get the image geometry
206     *
207     * @return array<int>
208     */
209    public function getGeometry(): array
210    {
211        return $this->geometry;
212    }
213
214    /**
215     * Get the image height
216     */
217    public function getHeight(): int
218    {
219        return (int) $this->geometry[1];
220    }
221
222    /**
223     * Get the image width
224     */
225    public function getWidth(): int
226    {
227        return (int) $this->geometry[0];
228    }
229
230    /**
231     * List the raw image information as an unordered list
232     */
233    public function listRaw(): string
234    {
235        $output = array();
236        $output[] = "<ul>";
237        $list = $this->verbose ? $this->verboseImageinfo : $this->imageinfo;
238        foreach ($list as $key => $line) {
239            if ($this->verbose) {
240                $output[] = "<li>" . $line . "</li>";
241            } else {
242                $output[] = "<li>" . $key . ' : ' . $line . "</li>";
243            }
244        }
245        $output[] = "<ul>";
246
247        return implode("\n", $output);
248    }
249
250    /**
251     * Search for the name of the file
252     */
253    private function parseFilename(): string
254    {
255        return $this->verbose ? $this->parseVerbose('Image') : $this->parse('Image');
256    }
257
258    /**
259     * Search for file format
260     */
261    private function parseFileformat(): void
262    {
263        $format = $this->verbose ? $this->parseVerbose('Format') : $this->parse('Format');
264        $matches = [];
265        preg_match("/^[\s]*[A-Z0-9]+/", $format, $matches);
266        if (count($matches) == 1) {
267            $this->format = strtolower(trim($matches[0]));
268        }
269    }
270
271    /**
272     * Search for image geometry
273     *
274     * @todo output not sanitized yet
275     */
276    private function parseGeometry(): void
277    {
278        $geometry = $this->verbose ? $this->parseVerbose('Geometry') : $this->parse('Geometry');
279        $matches = [];
280        preg_match("/^[0-9]*x[0-9]*/", $geometry, $matches);
281        if (isset($matches[0]) && $matches[0] !== '') {
282            $this->geometry = array_map('intval', explode("x", $matches[0]));
283        }
284    }
285
286    /**
287     * Search for image colorspace
288     */
289    private function parseColorspace(): void
290    {
291        $colorspace = $this->verbose ? $this->parseVerbose('Colorspace') : $this->parse('Colorspace');
292        $matches = [];
293        preg_match("/^[a-zA-Z0-9]*/", $colorspace, $matches);
294        $this->colorspace = isset($matches[0]) ? strtolower(trim($matches[0])) : '';
295    }
296
297    /**
298     * Search for image depth
299     */
300    private function parseDepth(): void
301    {
302        $depth = $this->verbose ? $this->parseVerbose('Depth') : $this->parse('Depth');
303        $matches = [];
304        preg_match("/^[0-9]*/", $depth, $matches);
305        $this->depth = isset($matches[0]) ? (int) $matches[0] : 0;
306    }
307
308    /**
309     * Search for image resolution
310     */
311    private function parseResolution(): void
312    {
313        $resolution = $this->verbose ? $this->parseVerbose('Resolution') : $this->parse('Resolution');
314        $this->resolution = array_map('intval', explode("x", $resolution));
315    }
316
317    /**
318     * Parse the image info array for the search line and
319     * return it for further processing
320     *
321     * @param string $search Search string
322     */
323    private function parseVerbose(string $search): string
324    {
325        foreach ($this->verboseImageinfo as $line) {
326            if (preg_match("/^" . $search . ":/", trim($line))) {
327                return trim(str_replace($search . ':', '', $line));
328            }
329        }
330        return '';
331    }
332
333    /**
334     * Parse the image info string for the search line and
335     * return it for further processing
336     *
337     * @param string $search Search string
338     */
339    private function parse(string $search): string
340    {
341        return $this->imageinfo[$search];
342    }
343
344    /**
345     * Search for print unit.
346     *
347     * If not found we default to Pixels Per Inch.
348     */
349    private function parseUnits(): void
350    {
351        $units = $this->verbose ? $this->parseVerbose('Units') : $this->parse('Units');
352        switch ($units) {
353            case 'PixelsPerCentimeter':
354                $this->isPpc = true;
355                $this->isPpi = false;
356                break;
357
358            case 'PixelsPerInch':
359                $this->isPpc = false;
360                $this->isPpi = true;
361                break;
362
363            case 'Undefined':
364                $this->isPpc = false;
365                $this->isPpi = true;
366                break;
367
368            default:
369                $this->isPpc = false;
370                $this->isPpi = true;
371                break;
372        }
373    }
374
375    /**
376     * Get the resolution unit
377     */
378    public function getUnit(): string
379    {
380        if ($this->isPpi) {
381            return 'Pixels Per Inch';
382        } else {
383            return 'Pixels Per Centimeter';
384        }
385    }
386
387    /**
388     * Get hash of file
389     *
390     * @param string $hash Name of hash algorithm to use; default is md5
391     */
392    public function getHash(string $hash = 'md5'): string
393    {
394        if ($hash == 'md5') {
395            $result = md5_file($this->getPathname());
396            if ($result === false) {
397                throw new \RuntimeException('Failed to calculate hash for file: ' . $this->getPathname());
398            }
399            return $result;
400        } else {
401            throw new \InvalidArgumentException($hash . ' is not a supported hash algorithm');
402        }
403    }
404
405    /**
406     * Convert Pixels Per Centimeter (ppc) to Pixels Per Inch (ppi)
407     *
408     * @param int $value Input value
409     */
410    private function ppc2ppi(int $value): int
411    {
412        return intval($value / 2.54);
413    }
414}