Cropper::fromPng()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
cc 2
eloc 11
c 5
b 0
f 0
nc 2
nop 6
dl 0
loc 18
rs 9.9
1
<?php
2
3
namespace CoffeeCode\Cropper;
4
5
use Exception;
6
use WebPConvert\Convert\Exceptions\ConversionFailedException;
7
use WebPConvert\WebPConvert;
8
9
/**
10
 * Class CoffeeCode Cropper
11
 *
12
 * @author Robson V. Leite <https://github.com/robsonvleite>
13
 * @package CoffeeCode\Cropper
14
 */
15
class Cropper
16
{
17
    /** @var string */
18
    private string $cachePath;
19
20
    /** @var string */
21
    private string $imagePath;
22
23
    /** @var string */
24
    private string $imageName;
25
26
    /** @var string */
27
    private string $imageMime;
28
29
    /** @var int */
30
    private int $quality;
31
32
    /** @var int */
33
    private int $compressor;
34
35
    /**@var bool */
36
    private bool $webP;
37
38
    /**
39
     * Allow jpg and png to thumb and cache generate
40
     * @var array allowed media types
41
     */
42
    private static array $allowedExt = ['image/jpeg', "image/png"];
43
44
    /**
45
     * Cropper constructor.
46
     * compressor must be 1-9
47
     * quality must be 1-100
48
     *
49
     * @param string $cachePath
50
     * @param int $quality
51
     * @param int $compressor
52
     * @param bool $webP
53
     * @throws Exception
54
     */
55
    public function __construct(string $cachePath, int $quality = 75, int $compressor = 5, bool $webP = false)
56
    {
57
        $this->cachePath = $cachePath;
58
        $this->quality = $quality;
59
        $this->compressor = $compressor;
60
        $this->webP = $webP;
61
62
        if (!file_exists($this->cachePath) || !is_dir($this->cachePath)) {
63
            if (!mkdir($this->cachePath, 0755, true)) {
64
                throw new Exception("Could not create cache folder");
65
            }
66
        }
67
    }
68
69
    /**
70
     * Make an thumb image
71
     *
72
     * @param string $imagePath
73
     * @param int $width
74
     * @param int|null $height
75
     * @return null|string
76
     */
77
    public function make(string $imagePath, int $width, int $height = null): ?string
78
    {
79
        if (!file_exists($imagePath)) {
80
            return "Image not found";
81
        }
82
83
        $this->imagePath = $imagePath;
84
        $this->imageName = $this->name($this->imagePath, $width, $height);
85
        $this->imageMime = mime_content_type($this->imagePath);
86
87
        if (!in_array($this->imageMime, self::$allowedExt)) {
88
            return "Not a valid JPG or PNG image";
89
        }
90
91
        return $this->image($width, $height);
92
    }
93
94
    /**
95
     * @param int $width
96
     * @param int|null $height
97
     * @return string|null
98
     */
99
    private function image(int $width, int $height = null): ?string
100
    {
101
        $imageWebP = "{$this->cachePath}/{$this->imageName}.webp";
102
        $imageExt = "{$this->cachePath}/{$this->imageName}." . pathinfo($this->imagePath)['extension'];
103
104
        if ($this->webP && file_exists($imageWebP) && is_file($imageWebP)) {
105
            return $imageWebP;
106
        }
107
108
        if (file_exists($imageExt) && is_file($imageExt)) {
109
            return $imageExt;
110
        }
111
112
        return $this->imageCache($width, $height);
113
    }
114
115
    /**
116
     * @param string $name
117
     * @param int|null $width
118
     * @param int|null $height
119
     * @return string
120
     */
121
    protected function name(string $name, int $width = null, int $height = null): string
122
    {
123
        $filterName = filter_var(mb_strtolower(pathinfo($name)["filename"]), FILTER_UNSAFE_RAW);
124
        $formats = 'ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜüÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûýýþÿRr"!@#$%&*()_-+={[}]/?;:.,\\\'<>°ºª';
125
        $replace = 'aaaaaaaceeeeiiiidnoooooouuuuuybsaaaaaaaceeeeiiiidnoooooouuuyybyRr                                 ';
126
        $trimName = trim(strtr(utf8_decode($filterName), utf8_decode($formats), $replace));
127
        $name = str_replace(["-----", "----", "---", "--"], "-", str_replace(" ", "-", $trimName));
128
129
        $hash = $this->hash($this->imagePath);
130
        $widthName = ($width ? "-{$width}" : "");
131
        $heightName = ($height ? "x{$height}" : "");
132
133
        return "{$name}{$widthName}{$heightName}-{$hash}";
134
    }
135
136
    /**
137
     * @param string $path
138
     * @return string
139
     */
140
    protected function hash(string $path): string
141
    {
142
        return hash("crc32", pathinfo($path)['basename']);
143
    }
144
145
    /**
146
     * Clear cache
147
     *
148
     * @param string|null $imagePath
149
     * @example $t->flush("images/image.jpg"); clear image name and variations size
150
     * @example $t->flush(); clear all image cache folder
151
     */
152
    public function flush(string $imagePath = null): void
153
    {
154
        foreach (scandir($this->cachePath) as $file) {
155
            $file = "{$this->cachePath}/{$file}";
156
            if ($imagePath && strpos($file, $this->hash($imagePath))) {
157
                $this->imageDestroy($file);
158
            } elseif (!$imagePath) {
159
                $this->imageDestroy($file);
160
            }
161
        }
162
    }
163
164
    /**
165
     * @param int $width
166
     * @param int|null $height
167
     * @return null|string
168
     */
169
    private function imageCache(int $width, int $height = null): ?string
170
    {
171
        list($src_w, $src_h) = getimagesize($this->imagePath);
172
        $height = ($height ?? ($width * $src_h) / $src_w);
173
174
        $src_x = 0;
175
        $src_y = 0;
176
177
        $cmp_x = $src_w / $width;
178
        $cmp_y = $src_h / $height;
179
180
        if ($cmp_x > $cmp_y) {
181
            $src_w = round($src_w / $cmp_x * $cmp_y);
182
            $src_x = round(($src_w - ($src_w / $cmp_x * $cmp_y))); //2
183
        } elseif ($cmp_y > $cmp_x) {
184
            $src_h = round($src_h / $cmp_y * $cmp_x);
185
            $src_y = round(($src_h - ($src_h / $cmp_y * $cmp_x))); //2
186
        }
187
188
        $height = (int)$height;
189
        $src_x = (int)$src_x;
190
        $src_y = (int)$src_y;
191
        $src_w = (int)$src_w;
192
        $src_h = (int)$src_h;
193
194
        if ($this->imageMime == "image/jpeg") {
195
            return $this->fromJpg($width, $height, $src_x, $src_y, $src_w, $src_h);
196
        }
197
198
        if ($this->imageMime == "image/png") {
199
            return $this->fromPng($width, $height, $src_x, $src_y, $src_w, $src_h);
200
        }
201
202
        return null;
203
    }
204
205
    /**
206
     * @param string $imagePatch
207
     */
208
    private function imageDestroy(string $imagePatch): void
209
    {
210
        if (file_exists($imagePatch) && is_file($imagePatch)) {
211
            unlink($imagePatch);
212
        }
213
    }
214
215
    /**
216
     * @param int $width
217
     * @param int $height
218
     * @param int $src_x
219
     * @param int $src_y
220
     * @param int $src_w
221
     * @param int $src_h
222
     * @return string
223
     */
224
    private function fromJpg(int $width, int $height, int $src_x, int $src_y, int $src_w, int $src_h): string
225
    {
226
        $thumb = imagecreatetruecolor($width, $height);
227
        $source = imagecreatefromjpeg($this->imagePath);
228
229
        imagecopyresampled($thumb, $source, 0, 0, $src_x, $src_y, $width, $height, $src_w, $src_h);
230
        imagejpeg($thumb, "{$this->cachePath}/{$this->imageName}.jpg", $this->quality);
231
232
        imagedestroy($thumb);
233
        imagedestroy($source);
234
235
        if ($this->webP) {
236
            return $this->toWebP("{$this->cachePath}/{$this->imageName}.jpg");
237
        }
238
239
        return "{$this->cachePath}/{$this->imageName}.jpg";
240
    }
241
242
    /**
243
     * @param int $width
244
     * @param int $height
245
     * @param int $src_x
246
     * @param int $src_y
247
     * @param int $src_w
248
     * @param int $src_h
249
     * @return string
250
     */
251
    private function fromPng(int $width, int $height, int $src_x, int $src_y, int $src_w, int $src_h): string
252
    {
253
        $thumb = imagecreatetruecolor($width, $height);
254
        $source = imagecreatefrompng($this->imagePath);
255
256
        imagealphablending($thumb, false);
257
        imagesavealpha($thumb, true);
258
        imagecopyresampled($thumb, $source, 0, 0, $src_x, $src_y, $width, $height, $src_w, $src_h);
259
        imagepng($thumb, "{$this->cachePath}/{$this->imageName}.png", $this->compressor);
260
261
        imagedestroy($thumb);
262
        imagedestroy($source);
263
264
        if ($this->webP) {
265
            return $this->toWebP("{$this->cachePath}/{$this->imageName}.png");
266
        }
267
268
        return "{$this->cachePath}/{$this->imageName}.png";
269
    }
270
271
    /**
272
     * @param string $image
273
     * @param bool $unlinkImage
274
     * @return string
275
     */
276
    public function toWebP(string $image, $unlinkImage = true): string
277
    {
278
        try {
279
            $webPConverted = pathinfo($image)["dirname"] . "/" . pathinfo($image)["filename"] . ".webp";
280
            WebPConvert::convert($image, $webPConverted, ["default-quality" => $this->quality]);
281
282
            if ($unlinkImage) {
283
                unlink($image);
284
            }
285
286
            return $webPConverted;
287
        } catch (ConversionFailedException $exception) {
288
            return $image;
289
        }
290
    }
291
}
292