Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
69.57% covered (warning)
69.57%
32 / 46
50.00% covered (danger)
50.00%
3 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
Karla
69.57% covered (warning)
69.57%
32 / 46
50.00% covered (danger)
50.00%
3 / 6
60.87
0.00% covered (danger)
0.00%
0 / 1
 perform
60.00% covered (warning)
60.00%
3 / 5
0.00% covered (danger)
0.00%
0 / 1
3.58
 __construct
65.22% covered (warning)
65.22%
15 / 23
0.00% covered (danger)
0.00%
0 / 1
24.47
 raw
55.56% covered (warning)
55.56%
5 / 9
0.00% covered (danger)
0.00%
0 / 1
13.62
 convert
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 identify
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 composite
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 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    2010-06-05
13 */
14
15declare(strict_types=1);
16
17namespace Karla;
18
19/**
20 * Karla core class
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 Karla
28{
29    /**
30     * Path to imagemagick binary
31     *
32     * @var string $binPath imagemagick binary
33     */
34    private string $binPath;
35
36    /**
37     * Cache controller
38     *
39     * @var Cache
40     */
41    private Cache|null $cache;
42
43    /**
44     * ImageMagick major version
45     *
46     * @var int|null
47     */
48    private int|null $version = null;
49
50    /**
51     * Instance of an ImageMagick object.
52     *
53     * @var Karla|null
54     */
55    private static Karla|null $instance = null;
56
57    /**
58     * Get an instance of Karla.
59     *
60     * @param string $binPath Path to ImageMagick binaries (optional)
61     * @param Cache|null $cache Cache controller (optional)
62     *
63     * @throws \InvalidArgumentException
64     */
65    public static function perform(string $binPath = '/opt/local/bin/', Cache|null $cache = null): Karla
66    {
67        if (! (Karla::$instance instanceof Karla)) {
68            try {
69                Karla::$instance = new Karla($binPath, $cache);
70            } catch (\InvalidArgumentException $e) {
71                throw new \RuntimeException($e->getMessage() . '(' . $binPath . ')');
72            }
73        }
74
75        return Karla::$instance;
76    }
77
78    /**
79     * Construct a new Karla object.
80     *
81     * @param string $binPath Path to ImageMagick binaries
82     * @param Cache|null $cache Cache controller
83     *
84     * @throws \InvalidArgumentException if path for ImageMagick is invalid
85     */
86    private function __construct(string $binPath, Cache|null $cache)
87    {
88        if (! file_exists($binPath)) {
89            throw new \InvalidArgumentException('Bin path not found');
90        }
91
92        // Detect ImageMagick version - try both v6 (convert) and v7 (magick)
93        $isWindows = strtoupper(substr(PHP_OS, 0, 3)) == "WIN";
94        $magickBin = $isWindows ? 'magick.exe' : 'magick';
95        $convertBin = $isWindows ? 'convert.exe' : 'convert';
96
97        $versionOutput = '';
98
99        // Try ImageMagick 7 first (magick command)
100        if (file_exists($binPath . $magickBin)) {
101            $versionOutput = shell_exec($binPath . $magickBin . ' -version');
102            if ($versionOutput && stripos($versionOutput, 'ImageMagick') !== false) {
103                // Extract major version (e.g., "Version: ImageMagick 7.1.2" -> 7)
104                if (preg_match('/ImageMagick (\d+)/', $versionOutput, $matches)) {
105                    $this->version = (int)$matches[1];
106                }
107            }
108        }
109
110        // Fallback to ImageMagick 6 (convert command)
111        if (empty($versionOutput) && file_exists($binPath . $convertBin)) {
112            $versionOutput = shell_exec($binPath . $convertBin . ' -version');
113            if ($versionOutput && stripos($versionOutput, 'ImageMagick') !== false) {
114                if (preg_match('/ImageMagick (\d+)/', $versionOutput, $matches)) {
115                    $this->version = (int)$matches[1];
116                } else {
117                    $this->version = 6; // Assume v6 if version not found but convert exists
118                }
119            }
120        }
121
122        if (empty($versionOutput)) {
123            throw new \InvalidArgumentException('ImageMagick could not be located at specified path');
124        }
125
126        // Set binPath appropriately for the OS
127        if (strtoupper(substr(PHP_OS, 0, 3)) == "WIN") {
128            // On Windows, just use the direct path
129            $this->binPath = rtrim($binPath, '/\\') . '/';
130        } else {
131            // On Unix, use export PATH
132            $this->binPath = 'export PATH=$PATH:' . $binPath . ';';
133        }
134
135        $this->cache = $cache;
136    }
137
138    /**
139     * Run a raw ImageMagick command
140     *
141     * Karla was never intended to wrap all of the functionality of ImageMagick
142     * and likely never will, you will from time to time need to write arguments
143     * to ImageMagick like you would have done directly in the console.
144     *
145     * @param string $program ImageMagick tool to use
146     * @param string $arguments Arguments for the tool
147     *
148     * @throws \InvalidArgumentException if you try to run a non ImageMagick program
149     */
150    public function raw(string $program, string $arguments = ""): string
151    {
152        if (! Program\ImageMagick::validProgram($program)) {
153            throw new \InvalidArgumentException('ImageMagick could not be located at specified path');
154        }
155        strtoupper(substr(PHP_OS, 0, 3)) == "WIN" ? $bin = $program . '.exe' : $bin = $program;
156
157        // For ImageMagick 7+, prepend with magick command
158        if ($this->version !== null && $this->version >= 7) {
159            $magickBin = strtoupper(substr(PHP_OS, 0, 3)) == "WIN" ?
160                Program\ImageMagick::IMAGEMAGICK_MAGICK . '.exe' : Program\ImageMagick::IMAGEMAGICK_MAGICK;
161            $result = shell_exec($this->binPath . $magickBin . ' ' . $program . ' ' . $arguments);
162        } else {
163            $result = shell_exec($this->binPath . $bin . ' ' . $arguments);
164        }
165
166        return $result !== null && $result !== false ? $result : '';
167    }
168
169    /**
170     * Start a convert operation
171     */
172    public function convert(): Program\Convert
173    {
174        $bin = strtoupper(substr(PHP_OS, 0, 3)) == "WIN" ?
175            Program\ImageMagick::IMAGEMAGICK_CONVERT . '.exe' : Program\ImageMagick::IMAGEMAGICK_CONVERT;
176
177        return new Program\Convert($this->binPath, $bin, $this->cache, $this->version);
178    }
179
180    /**
181     * Start an identify operation
182     */
183    public function identify(): Program\Identify
184    {
185        $bin = strtoupper(substr(PHP_OS, 0, 3)) == "WIN" ?
186            Program\ImageMagick::IMAGEMAGICK_IDENTIFY . '.exe' : Program\ImageMagick::IMAGEMAGICK_IDENTIFY;
187
188        return new Program\Identify($this->binPath, $bin, $this->cache, $this->version);
189    }
190
191    /**
192     * Start a composite operation
193     */
194    public function composite(): Program\Composite
195    {
196        $bin = strtoupper(substr(PHP_OS, 0, 3)) == "WIN" ?
197            Program\ImageMagick::IMAGEMAGICK_COMPOSITE . '.exe' : Program\ImageMagick::IMAGEMAGICK_COMPOSITE;
198
199        return new Program\Composite($this->binPath, $bin, $this->cache, $this->version);
200    }
201}