Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
85.71% covered (warning)
85.71%
90 / 105
75.00% covered (warning)
75.00%
18 / 24
CRAP
0.00% covered (danger)
0.00%
0 / 1
MetaData
85.71% covered (warning)
85.71%
90 / 105
75.00% covered (warning)
75.00%
18 / 24
57.29
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
50.00% covered (danger)
50.00%
5 / 10
0.00% covered (danger)
0.00%
0 / 1
4.12
 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
90.91% covered (success)
90.91%
10 / 11
0.00% covered (danger)
0.00%
0 / 1
5.02
 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
3
 parseColorspace
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 parseDepth
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 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%
3 / 3
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
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 ppc2ppi
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 ppi2ppc
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 string[]
33     */
34    private array $verboseImageinfo;
35
36    /**
37     * Raw image info
38     *
39     * @var array
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 integer
54     */
55    private int $depth;
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
68     */
69    private array $geometry;
70
71    /**
72     * The image resolution
73     *
74     * @var array
75     */
76    private array $resolution;
77
78    /**
79     * The resolution unit is measured in Pixels Per Inch
80     *
81     * @var boolean
82     */
83    private bool $isPpi;
84
85    /**
86     * The resolution unit is measured in Pixels Per Centimeter
87     *
88     * @var boolean
89     */
90    private bool $isPpc;
91
92    /**
93     * Are we scanning in verbose output from identify
94     *
95     * @var boolean
96     */
97    private bool $verbose;
98
99    /**
100     * Construct a new image file.
101     *
102     * @param string $imageinfo
103     *            Image info as string
104     * @param boolean $verbose
105     *            Should input be parsed as verbose
106     */
107    public function __construct(string $imageinfo, bool $verbose = false)
108    {
109        $this->verbose = $verbose;
110        if ($this->verbose) {
111            $this->verboseImageinfo = explode("\n", $imageinfo);
112            $this->parseFileformat();
113            $this->parseGeometry();
114            $this->parseResolution();
115            $this->parseUnits();
116            $this->parseDepth();
117            $this->parseColorspace();
118            parent::__construct($this->parseFilename());
119        } else {
120            $info = explode(" ", $imageinfo);
121            $this->imageinfo['Image'] = $info[0];
122            $this->imageinfo['Format'] = $info[1];
123            $this->imageinfo['Geometry'] = $info[3];
124            $this->imageinfo['Depth'] = $info[4];
125            $this->imageinfo['Colorspace'] = $info[5];
126            $this->imageinfo['Resolution'] = 'x';
127            $this->imageinfo['Units'] = '';
128            $this->parseFileformat();
129            $this->parseGeometry();
130            $this->parseResolution();
131            $this->parseUnits();
132            $this->parseDepth();
133            $this->parseColorspace();
134            parent::__construct($this->parseFilename());
135        }
136    }
137
138    /**
139     * Get the image resolution.
140     * If the resolution is in ppc we convert it to ppi
141     *
142     * @return array
143     */
144    private function getResolution(): array
145    {
146        if ($this->verbose) {
147            if ($this->isPpc) {
148                // Here we convert to ppi
149                $this->resolution = array(
150                    $this->ppc2ppi($this->resolution[0]),
151                    $this->ppc2ppi($this->resolution[1])
152                );
153                $this->isPpc = false;
154                $this->isPpi = true;
155            }
156        } else {
157            $this->resolution = array();
158        }
159
160        return $this->resolution;
161    }
162
163    /**
164     * Get the resolution height.
165     *
166     * @return integer
167     */
168    public function getResolutionHeight(): int|null
169    {
170        $res = $this->getResolution();
171        return isset($res[1]) ? (int) $res[1] : null;
172    }
173
174    /**
175     * Get the resolution width.
176     *
177     * @return integer
178     */
179    public function getResolutionWidth(): int|null
180    {
181        $res = $this->getResolution();
182
183        return isset($res[0]) ? (int) $res[0] : null;
184    }
185
186    /**
187     * Get file format
188     *
189     * @return string
190     */
191    public function getFormat(): string
192    {
193        return $this->format;
194    }
195
196    /**
197     * Get image depth
198     *
199     * @return integer
200     */
201    public function getDepth(): int
202    {
203        return $this->depth;
204    }
205
206    /**
207     * Get colorspace
208     *
209     * @return string
210     */
211    public function getColorspace(): string
212    {
213        return $this->colorspace;
214    }
215
216    /**
217     * Get the image geometry
218     *
219     * @return array
220     */
221    public function getGeometry(): array
222    {
223        return $this->geometry;
224    }
225
226    /**
227     * Get the image height
228     *
229     * @return integer
230     */
231    public function getHeight(): int
232    {
233        return (int) $this->geometry[1];
234    }
235
236    /**
237     * Get the image width
238     *
239     * @return integer
240     */
241    public function getWidth(): int
242    {
243        return (int) $this->geometry[0];
244    }
245
246    /**
247     * List the raw image information as a unorder list
248     *
249     * @return string
250     */
251    public function listRaw(): string
252    {
253        $output = array();
254        $output[] = "<ul>";
255        $list = $this->verbose ? $this->verboseImageinfo : $this->imageinfo;
256        if (is_array($list)) {
257            foreach ($list as $key => $line) {
258                if ($this->verbose) {
259                    $output[] = "<li>" . $line . "</li>";
260                } else {
261                    $output[] = "<li>" . $key . ' : ' . $line . "</li>";
262                }
263            }
264        } else {
265            $output[] = "<li>" . $this->imageinfo . "</li>";
266        }
267        $output[] = "<ul>";
268
269        return implode("\n", $output);
270    }
271
272    /**
273     * Search for the name of the file
274     *
275     * @return string
276     */
277    private function parseFilename(): string
278    {
279        return $this->verbose ? $this->parseVerbose('Image') : $this->parse('Image');
280    }
281
282    /**
283     * Search for file format
284     *
285     * @return void
286     */
287    private function parseFileformat(): void
288    {
289        $format = $this->verbose ? $this->parseVerbose('Format') : $this->parse('Format');
290        $matches = [];
291        preg_match("/^[\s]*[A-Z0-9]+/", $format, $matches);
292        if (count($matches) == 1) {
293            $this->format = strtolower(trim($matches[0]));
294        }
295    }
296
297    /**
298     * Search for image geometry
299     *
300     * @todo output not sanetized yet
301     *
302     * @return void
303     */
304    private function parseGeometry(): void
305    {
306        $geometry = $this->verbose ? $this->parseVerbose('Geometry') : $this->parse('Geometry');
307        $matches = [];
308        preg_match("/^[0-9]*x[0-9]*/", $geometry, $matches);
309        if (count($matches) == 1) {
310            $this->geometry = explode("x", $matches[0]);
311        }
312    }
313
314    /**
315     * Search for image colorspace
316     *
317     * @return void
318     */
319    private function parseColorspace(): void
320    {
321        $colorspace = $this->verbose ? $this->parseVerbose('Colorspace') : $this->parse('Colorspace');
322        $matches = [];
323        preg_match("/^[a-zA-Z0-9]*/", $colorspace, $matches);
324        $this->colorspace = strtolower(trim($matches[0]));
325    }
326
327    /**
328     * Search for image depth
329     *
330     * @return void
331     */
332    private function parseDepth(): void
333    {
334        $depth = $this->verbose ? $this->parseVerbose('Depth') : $this->parse('Depth');
335        $matches = [];
336        preg_match("/^[0-9]*/", $depth, $matches);
337        $this->depth = (int) $matches[0];
338    }
339
340    /**
341     * Search for image resolution
342     *
343     * @return void
344     */
345    private function parseResolution(): void
346    {
347        $resolution = $this->verbose ? $this->parseVerbose('Resolution') : $this->parse('Resolution');
348        $this->resolution = explode("x", $resolution);
349    }
350
351    /**
352     * Parse the image info array for the search line and
353     * return it for futher processing
354     *
355     * @param string $search
356     *            Search string
357     *
358     * @return string
359     */
360    private function parseVerbose($search): string
361    {
362        foreach ($this->verboseImageinfo as $line) {
363            if (preg_match("/^" . $search . ":/", trim($line))) {
364                return trim(str_replace($search . ':', '', $line));
365            }
366        }
367    }
368
369    /**
370     * Parse the image info string for the search line and
371     * return it for futher processing
372     *
373     * @param string $search
374     *            Search string
375     *
376     * @return string
377     */
378    private function parse($search): string
379    {
380        return $this->imageinfo[$search];
381    }
382
383    /**
384     * Search for print unit.
385     *
386     * If not found we default to Pixels Per Inch.
387     *
388     * @return void
389     */
390    private function parseUnits(): void
391    {
392        $units = $this->verbose ? $this->parseVerbose('Units') : $this->parse('Units');
393        switch ($units) {
394            case 'PixelsPerCentimeter':
395                $this->isPpc = true;
396                $this->isPpi = false;
397                break;
398
399            case 'PixelsPerInch':
400                $this->isPpc = false;
401                $this->isPpi = true;
402                break;
403
404            case 'Undefined':
405                $this->isPpc = false;
406                $this->isPpi = true;
407                break;
408
409            default:
410                $this->isPpc = false;
411                $this->isPpi = true;
412                break;
413        }
414    }
415
416    /**
417     * Get the resolution unit
418     *
419     * @return string
420     */
421    public function getUnit(): string
422    {
423        if ($this->isPpi) {
424            return 'Pixels Per Inch';
425        } else {
426            return 'Pixels Per Centimeter';
427        }
428    }
429
430    /**
431     * Get hash of file
432     *
433     * @param string $hash
434     *            Name of hash algorithm to use; default is md5
435     *
436     * @return string
437     */
438    public function getHash(string $hash = 'md5'): string
439    {
440        if ($hash == 'md5') {
441            return md5_file($this->getPathname());
442        } else {
443            throw new \InvalidArgumentException($hash . ' is not a supported hash algorithm');
444        }
445    }
446
447    /**
448     * Convert Pixels Per Centimeter (ppc) to Pixels Per Inch (ppi)
449     *
450     * @param integer $value
451     *            Input value
452     *
453     * @return integer
454     */
455    private function ppc2ppi(int $value): int
456    {
457        return intval(floor($value * 0.3937008));
458    }
459
460    /**
461     * Convert Pixels Per Inch (ppi) to Pixels Per Centimeter (ppc)
462     *
463     * @param integer $value
464     *            Input value
465     *
466     * @return integer
467     */
468    private function ppi2ppc(int $value): int
469    {
470        return intval(floor($value * 2.54));
471    }
472}