Completed
Push — master ( 28907f...d3c247 )
by Loz
15:07 queued 05:05
created

ImagickBackend::getImageResource()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 5
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
rs 9.4285
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 16 and the first side effect is on line 13.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
3
namespace SilverStripe\Assets;
4
5
use SilverStripe\Assets\Storage\AssetContainer;
6
use SilverStripe\Assets\Storage\AssetStore;
7
use SilverStripe\Core\Config\Config;
8
use Imagick;
9
use InvalidArgumentException;
10
use ImagickPixel;
11
12
if (!class_exists('Imagick')) {
13
    return;
14
}
15
16
class ImagickBackend extends Imagick implements Image_Backend
17
{
18
19
    /**
20
     * @config
21
     * @var int
22
     */
23
    private static $default_quality = 75;
24
25
    /**
26
     * @return $this
27
     */
28
    public function getImageResource()
29
    {
30
        // the object represents the resource
31
        return $this;
32
    }
33
34
    /**
35
     * Create a new backend with the given object
36
     *
37
     * @param AssetContainer $assetContainer Object to load from
38
     */
39
    public function __construct(AssetContainer $assetContainer = null)
40
    {
41
        parent::__construct();
42
43
        if ($assetContainer) {
44
            $this->loadFromContainer($assetContainer);
45
        }
46
    }
47
48
    public function loadFromContainer(AssetContainer $assetContainer)
49
    {
50
        $stream = $assetContainer->getStream();
51
        $this->readImageFile($stream);
52
        fclose($stream);
53
        $this->setDefaultQuality();
54
    }
55
56
    public function loadFrom($path)
57
    {
58
        $this->readImage($path);
59
        $this->setDefaultQuality();
60
    }
61
62
    protected function setDefaultQuality()
63
    {
64
        $this->setQuality(Config::inst()->get('SilverStripe\\Assets\\ImagickBackend', 'default_quality'));
65
    }
66
67
    public function writeToStore(AssetStore $assetStore, $filename, $hash = null, $variant = null, $config = array())
68
    {
69
        // Write to temporary file, taking care to maintain the extension
70
        $path = tempnam(sys_get_temp_dir(), 'imagemagick');
71
        if ($extension = pathinfo($filename, PATHINFO_EXTENSION)) {
72
            $path .= "." . $extension;
73
        }
74
        $this->writeImage($path);
75
        $result = $assetStore->setFromLocalFile($path, $filename, $hash, $variant, $config);
76
        unlink($path);
77
        return $result;
78
    }
79
80
    public function writeTo($path)
81
    {
82
        Filesystem::makeFolder(dirname($path));
83
        if (is_dir(dirname($path))) {
84
            $this->writeImage($path);
85
        }
86
    }
87
88
    public function setQuality($quality)
89
    {
90
        $this->setImageCompressionQuality($quality);
91
    }
92
93
    public function resize($width, $height)
94
    {
95
        if (!$this->valid()) {
96
            return null;
97
        }
98
99
        if ($width < 0 || $height < 0) {
100
            throw new InvalidArgumentException("Image resizing dimensions cannot be negative");
101
        }
102
        if (!$width && !$height) {
103
            throw new InvalidArgumentException("No dimensions given when resizing image");
104
        }
105
        if (!$width) {
106
            throw new InvalidArgumentException("Width not given when resizing image");
107
        }
108
        if (!$height) {
109
            throw new InvalidArgumentException("Height not given when resizing image");
110
        }
111
112
        //use whole numbers, ensuring that size is at least 1x1
113
        $width = max(1, round($width));
114
        $height = max(1, round($height));
115
116
        $geometry = $this->getImageGeometry();
117
118
        // Check that a resize is actually necessary.
119
        if ($width === $geometry["width"] && $height === $geometry["height"]) {
120
            return $this;
121
        }
122
123
        $new = clone $this;
124
        $new->resizeImage($width, $height, self::FILTER_LANCZOS, 1);
125
126
        return $new;
127
    }
128
129
    public function resizeRatio($maxWidth, $maxHeight, $useAsMinimum = false)
130
    {
131
        if (!$this->valid()) {
132
            return null;
133
        }
134
135
        $geometry = $this->getImageGeometry();
136
137
        $widthRatio = $maxWidth / $geometry["width"];
138
        $heightRatio = $maxHeight / $geometry["height"];
139
140
        if ($widthRatio < $heightRatio) {
141
            return $useAsMinimum
142
                ? $this->resizeByHeight($maxHeight)
143
                : $this->resizeByWidth($maxWidth);
144
        } else {
145
            return $useAsMinimum
146
                ? $this->resizeByWidth($maxWidth)
147
                : $this->resizeByHeight($maxHeight);
148
        }
149
    }
150
151
    public function resizeByWidth($width)
152
    {
153
        if (!$this->valid()) {
154
            return null;
155
        }
156
157
        $geometry = $this->getImageGeometry();
158
159
        $heightScale = $width / $geometry["width"];
160
        return $this->resize($width, $heightScale * $geometry["height"]);
161
    }
162
163
    public function resizeByHeight($height)
164
    {
165
        if (!$this->valid()) {
166
            return null;
167
        }
168
169
        $geometry = $this->getImageGeometry();
170
171
        $scale = $height / $geometry["height"];
172
        return $this->resize($scale * $geometry["width"], $height);
173
    }
174
175
    /**
176
     * paddedResize
177
     *
178
     * @param int $width
179
     * @param int $height
180
     * @param string $backgroundColor
181
     * @param int $transparencyPercent
182
     * @return Image_Backend
183
     */
184
    public function paddedResize($width, $height, $backgroundColor = "FFFFFF", $transparencyPercent = 0)
185
    {
186
        if (!$this->valid()) {
187
            return null;
188
        }
189
190
        //keep the % within bounds of 0-100
191
        $transparencyPercent = min(100, max(0, $transparencyPercent));
192
193
        $new = $this->resizeRatio($width, $height);
194
        if ($transparencyPercent) {
195
            $alphaHex = $this->calculateAlphaHex($transparencyPercent);
196
            $new->setImageBackgroundColor("#{$backgroundColor}{$alphaHex}");
197
        } else {
198
            $new->setImageBackgroundColor("#{$backgroundColor}");
199
        }
200
        $w = $new->getImageWidth();
201
        $h = $new->getImageHeight();
202
        $new->extentImage($width, $height, ($w-$width)/2, ($h-$height)/2);
203
        return $new;
204
    }
205
206
    /**
207
     * Convert a percentage (or 'true') to a two char hex code to signifiy the level of an alpha channel
208
     *
209
     * @param $percent
210
     * @return string
211
     */
212
    public function calculateAlphaHex($percent)
213
    {
214
        if ($percent > 100) {
215
            $percent = 100;
216
        }
217
        // unlike GD, this uses 255 instead of 127, and is reversed. Lower = more transparent
218
        $alphaHex = dechex(255 - floor(255 * bcdiv($percent, 100, 2)));
219
        if (strlen($alphaHex) == 1) {
220
            $alphaHex =  '0' .$alphaHex;
221
        }
222
        return $alphaHex;
223
    }
224
225
226
    /**
227
     * croppedResize
228
     *
229
     * @param int $width
230
     * @param int $height
231
     * @return Image_Backend
232
     */
233
    public function croppedResize($width, $height)
234
    {
235
        if (!$this->valid()) {
236
            return null;
237
        }
238
239
        $width = round($width);
240
        $height = round($height);
241
        $geo = $this->getImageGeometry();
242
243
        // Check that a resize is actually necessary.
244
        if ($width == $geo["width"] && $height == $geo["height"]) {
245
            return $this;
246
        }
247
248
        $new = clone $this;
249
        $new->setBackgroundColor(new ImagickPixel('transparent'));
250
251
        if (($geo['width']/$width) < ($geo['height']/$height)) {
252
            $new->cropImage(
253
                $geo['width'],
254
                floor($height*$geo['width']/$width),
255
                0,
256
                ($geo['height'] - ($height*$geo['width']/$width))/2
257
            );
258
        } else {
259
            $new->cropImage(
260
                ceil($width*$geo['height']/$height),
261
                $geo['height'],
262
                ($geo['width'] - ($width*$geo['height']/$height))/2,
263
                0
264
            );
265
        }
266
        $new->thumbnailImage($width, $height, true);
267
        return $new;
268
    }
269
270
    /**
271
     * Crop's part of image.
272
     * @param int $top y position of left upper corner of crop rectangle
273
     * @param int $left x position of left upper corner of crop rectangle
274
     * @param int $width rectangle width
275
     * @param int $height rectangle height
276
     * @return Image_Backend
277
     */
278
    public function crop($top, $left, $width, $height)
279
    {
280
        $new = clone $this;
281
        $new->cropImage($width, $height, $left, $top);
282
283
        return $new;
284
    }
285
}
286