1: <?php
2: /**
3: * @package OpenCart
4: *
5: * @author Daniel Kerr
6: * @copyright Copyright (c) 2005 - 2022, OpenCart, Ltd. (https://www.opencart.com/)
7: * @license https://opensource.org/licenses/GPL-3.0
8: *
9: * @see https://www.opencart.com
10: */
11: namespace Opencart\System\Library;
12: /**
13: * Class Image
14: */
15: class Image {
16: /**
17: * @var string
18: */
19: private string $file;
20: /**
21: * @var mixed
22: */
23: private $image;
24: /**
25: * @var int
26: */
27: private int $width;
28: /**
29: * @var int
30: */
31: private int $height;
32: /**
33: * @var string
34: */
35: private string $bits;
36: /**
37: * @var string
38: */
39: private string $mime;
40:
41: /**
42: * Constructor
43: *
44: * @param string $file
45: */
46: public function __construct(string $file) {
47: if (!extension_loaded('gd')) {
48: exit('Error: PHP GD is not installed!');
49: }
50:
51: if (is_file($file)) {
52: $this->file = $file;
53:
54: $info = getimagesize($file);
55:
56: $this->width = $info[0];
57: $this->height = $info[1];
58: $this->bits = $info['bits'] ?? '';
59: $this->mime = $info['mime'] ?? '';
60:
61: if ($this->mime == 'image/gif') {
62: $this->image = imagecreatefromgif($file);
63: } elseif ($this->mime == 'image/png') {
64: $this->image = imagecreatefrompng($file);
65:
66: imageinterlace($this->image, false);
67: } elseif ($this->mime == 'image/jpeg') {
68: $this->image = imagecreatefromjpeg($file);
69: } elseif ($this->mime == 'image/webp') {
70: $this->image = imagecreatefromwebp($file);
71: }
72: } else {
73: throw new \Exception('Error: Could not load image ' . $file . '!');
74: }
75: }
76:
77: /**
78: * getFile
79: *
80: * @return string
81: */
82: public function getFile(): string {
83: return $this->file;
84: }
85:
86: /**
87: * getImage
88: *
89: * @return mixed
90: */
91: public function getImage() {
92: return $this->image ?: null;
93: }
94:
95: /**
96: * getWidth
97: *
98: * @return int
99: */
100: public function getWidth(): int {
101: return $this->width;
102: }
103:
104: /**
105: * getHeight
106: *
107: * @return int
108: */
109: public function getHeight(): int {
110: return $this->height;
111: }
112:
113: /**
114: * getBits
115: *
116: * @return string
117: */
118: public function getBits(): string {
119: return $this->bits;
120: }
121:
122: /**
123: * getMime
124: *
125: * @return string
126: */
127: public function getMime(): string {
128: return $this->mime;
129: }
130:
131: /**
132: * Save
133: *
134: * @param string $file
135: * @param int $quality
136: *
137: * @return void
138: */
139: public function save(string $file, int $quality = 90): void {
140: $info = pathinfo($file);
141:
142: $extension = strtolower($info['extension']);
143:
144: if (is_object($this->image) || is_resource($this->image)) {
145: if ($extension == 'jpeg' || $extension == 'jpg') {
146: imagejpeg($this->image, $file, $quality);
147: } elseif ($extension == 'png') {
148: imagepng($this->image, $file);
149: } elseif ($extension == 'gif') {
150: imagegif($this->image, $file);
151: } elseif ($extension == 'webp') {
152: imagewebp($this->image, $file);
153: }
154:
155: imagedestroy($this->image);
156: }
157: }
158:
159: /**
160: * Resize
161: *
162: * @param int $width
163: * @param int $height
164: * @param string $default
165: *
166: * @return void
167: */
168: public function resize(int $width = 0, int $height = 0, string $default = ''): void {
169: if (!$this->width || !$this->height) {
170: return;
171: }
172:
173: $xpos = 0;
174: $ypos = 0;
175: $scale = 1;
176:
177: $scale_w = $width / $this->width;
178: $scale_h = $height / $this->height;
179:
180: if ($default == 'w') {
181: $scale = $scale_w;
182: } elseif ($default == 'h') {
183: $scale = $scale_h;
184: } else {
185: $scale = min($scale_w, $scale_h);
186: }
187:
188: if ($scale == 1 && $scale_h == $scale_w && ($this->mime != 'image/png' && $this->mime != 'image/webp')) {
189: return;
190: }
191:
192: $new_width = (int)($this->width * $scale);
193: $new_height = (int)($this->height * $scale);
194: $xpos = (int)(($width - $new_width) / 2);
195: $ypos = (int)(($height - $new_height) / 2);
196:
197: $image_old = $this->image;
198: $this->image = imagecreatetruecolor($width, $height);
199:
200: if ($this->mime == 'image/png') {
201: imagealphablending($this->image, false);
202: imagesavealpha($this->image, true);
203:
204: $background = imagecolorallocatealpha($this->image, 255, 255, 255, 127);
205:
206: imagecolortransparent($this->image, $background);
207: } elseif ($this->mime == 'image/webp') {
208: imagealphablending($this->image, false);
209: imagesavealpha($this->image, true);
210:
211: $background = imagecolorallocatealpha($this->image, 255, 255, 255, 127);
212:
213: imagecolortransparent($this->image, $background);
214: } else {
215: $background = imagecolorallocate($this->image, 255, 255, 255);
216: }
217:
218: imagefilledrectangle($this->image, 0, 0, $width, $height, $background);
219:
220: imagecopyresampled($this->image, $image_old, $xpos, $ypos, 0, 0, $new_width, $new_height, $this->width, $this->height);
221: imagedestroy($image_old);
222:
223: $this->width = $width;
224: $this->height = $height;
225: }
226:
227: /**
228: * Watermark
229: *
230: * @param self $watermark
231: * @param string $position
232: *
233: * @return void
234: */
235: public function watermark(self $watermark, string $position = 'bottomright'): void {
236: switch ($position) {
237: case 'topleft':
238: $watermark_pos_x = 0;
239: $watermark_pos_y = 0;
240: break;
241: case 'topcenter':
242: $watermark_pos_x = (int)(($this->width - $watermark->getWidth()) / 2);
243: $watermark_pos_y = 0;
244: break;
245: case 'topright':
246: $watermark_pos_x = ($this->width - $watermark->getWidth());
247: $watermark_pos_y = 0;
248: break;
249: case 'middleleft':
250: $watermark_pos_x = 0;
251: $watermark_pos_y = (int)(($this->height - $watermark->getHeight()) / 2);
252: break;
253: case 'middlecenter':
254: $watermark_pos_x = (int)(($this->width - $watermark->getWidth()) / 2);
255: $watermark_pos_y = (int)(($this->height - $watermark->getHeight()) / 2);
256: break;
257: case 'middleright':
258: $watermark_pos_x = ($this->width - $watermark->getWidth());
259: $watermark_pos_y = (int)(($this->height - $watermark->getHeight()) / 2);
260: break;
261: case 'bottomleft':
262: $watermark_pos_x = 0;
263: $watermark_pos_y = ($this->height - $watermark->getHeight());
264: break;
265: case 'bottomcenter':
266: $watermark_pos_x = (int)(($this->width - $watermark->getWidth()) / 2);
267: $watermark_pos_y = ($this->height - $watermark->getHeight());
268: break;
269: case 'bottomright':
270: $watermark_pos_x = ($this->width - $watermark->getWidth());
271: $watermark_pos_y = ($this->height - $watermark->getHeight());
272: break;
273: default:
274: $watermark_pos_x = 0;
275: $watermark_pos_y = 0;
276: break;
277: }
278:
279: imagealphablending($this->image, true);
280: imagesavealpha($this->image, true);
281: imagecopy($this->image, $watermark->getImage(), $watermark_pos_x, $watermark_pos_y, 0, 0, $watermark->getWidth(), $watermark->getHeight());
282:
283: imagedestroy($watermark->getImage());
284: }
285:
286: /**
287: * Crop
288: *
289: * @param int $top_x
290: * @param int $top_y
291: * @param int $bottom_x
292: * @param int $bottom_y
293: *
294: * @return void
295: */
296: public function crop(int $top_x, int $top_y, int $bottom_x, int $bottom_y): void {
297: $image_old = $this->image;
298: $this->image = imagecreatetruecolor($bottom_x - $top_x, $bottom_y - $top_y);
299:
300: imagecopy($this->image, $image_old, 0, 0, $top_x, $top_y, $this->width, $this->height);
301: imagedestroy($image_old);
302:
303: $this->width = $bottom_x - $top_x;
304: $this->height = $bottom_y - $top_y;
305: }
306:
307: /**
308: * Rotate
309: *
310: * @param int $degree
311: * @param string $color
312: *
313: * @return void
314: */
315: public function rotate(int $degree, string $color = 'FFFFFF'): void {
316: $rgb = $this->html2rgb($color);
317:
318: $this->image = imagerotate($this->image, $degree, imagecolorallocate($this->image, $rgb[0], $rgb[1], $rgb[2]));
319:
320: $this->width = imagesx($this->image);
321: $this->height = imagesy($this->image);
322: }
323:
324: /**
325: * Filter
326: *
327: * @return void
328: */
329: private function filter(): void {
330: $args = func_get_args();
331:
332: imagefilter(...$args);
333: }
334:
335: /**
336: * Text
337: *
338: * @param string $text
339: * @param int $x
340: * @param int $y
341: * @param int $size
342: * @param string $color
343: *
344: * @return void
345: */
346: private function text(string $text, int $x = 0, int $y = 0, int $size = 5, string $color = '000000'): void {
347: $rgb = $this->html2rgb($color);
348:
349: imagestring($this->image, $size, $x, $y, $text, imagecolorallocate($this->image, $rgb[0], $rgb[1], $rgb[2]));
350: }
351:
352: /**
353: * Merge
354: *
355: * @param self $merge
356: * @param int $x
357: * @param int $y
358: * @param int $opacity
359: *
360: * @return void
361: */
362: private function merge(self $merge, int $x = 0, int $y = 0, int $opacity = 100): void {
363: imagecopymerge($this->image, $merge->getImage(), $x, $y, 0, 0, $merge->getWidth(), $merge->getHeight(), $opacity);
364: }
365:
366: /**
367: * HTML2RGB
368: *
369: * @param string $color
370: *
371: * @return array<int, int>
372: */
373: private function html2rgb(string $color): array {
374: if ($color[0] == '#') {
375: $color = substr($color, 1);
376: }
377:
378: if (strlen($color) == 6) {
379: [
380: $r,
381: $g,
382: $b
383: ] = [
384: $color[0] . $color[1],
385: $color[2] . $color[3],
386: $color[4] . $color[5]
387: ];
388: } elseif (strlen($color) == 3) {
389: [
390: $r,
391: $g,
392: $b
393: ] = [
394: $color[0] . $color[0],
395: $color[1] . $color[1],
396: $color[2] . $color[2]
397: ];
398: } else {
399: return [];
400: }
401:
402: $r = hexdec($r);
403: $g = hexdec($g);
404: $b = hexdec($b);
405:
406: return [
407: $r,
408: $g,
409: $b
410: ];
411: }
412: }
413: