PluginProbe
Sharing Image / 2.0.13
Sharing Image v2.0.13
3.10 trunk 2.0 2.0.0 2.0.1 2.0.10 2.0.11 2.0.12 2.0.13 2.0.14 2.0.15 2.0.16 2.0.17 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 3.0 3.1 3.2 3.3 All 29 releases
sharing-image / vendor / antonlukin / poster-editor / src / PosterEditor.php

PosterEditor.php in Sharing Image 2.0.13, at vendor/antonlukin/poster-editor/src/PosterEditor.php

1,237 lines 32.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Wrapper for PHP's GD Library for easy image manipulation to resize, crop
4 * and draw images on top of each other preserving transparency, writing text
5 * with transparency and drawing shapes.
6 * php version 7.1
7 *
8 * @category PHP
9 * @package PosterEditor
10 * @author Anton Lukin <[email protected]>
11 * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
12 * @link https://github.com/antonlukin/poster-editor
13 */
14
15 namespace PosterEditor;
16
17 use Exception;
18
19 /**
20 * Draw images, text and shapes using php-gd.
21 *
22 * @category PHP
23 * @package PosterEditor
24 * @author Anton Lukin <[email protected]>
25 * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
26 * @version Release: 5.7
27 * @link https://github.com/antonlukin/poster-editor
28 */
29 class PosterEditor
30 {
31 /**
32 * Canvas resource
33 *
34 * @var resource
35 */
36 protected $resource;
37
38 /**
39 * Canvas width
40 *
41 * @var integer
42 */
43 protected $width;
44
45 /**
46 * Canvas height
47 *
48 * @var integer
49 */
50 protected $height;
51
52 /**
53 * Image type
54 *
55 * @var integer
56 */
57 protected $type;
58
59 /**
60 * Initialise the image.
61 */
62 public function __construct()
63 {
64 if (!extension_loaded('gd') && !extension_loaded('gd2')) {
65 $this->handleError('Extension php-gd is not loaded');
66 }
67 }
68
69 /**
70 * Get image resource to use raw gd commands.
71 *
72 * @return resource
73 */
74 public function get()
75 {
76 return $this->resource;
77 }
78
79 /**
80 * Set image resource after using raw gd commands.
81 *
82 * @param instance $resource Image resource.
83 *
84 * @return $this
85 */
86 public function set($resource)
87 {
88 $this->resource = $resource;
89
90 $this->width = imagesx($resource);
91 $this->height = imagesy($resource);
92
93 return $this;
94 }
95
96 /**
97 * Make new image instance from file or binary data.
98 *
99 * @param string $data Binary data or path to file.
100 *
101 * @return $this
102 */
103 public function make($data)
104 {
105 switch (true) {
106 case $this->isBinary($data):
107 $image = $this->createFromString($data);
108 break;
109
110 default:
111 $image = $this->createFromFile($data);
112 }
113
114 list($width, $height, $type, $source) = $image;
115
116 $this->copyResampled($source, 0, 0, 0, 0, $width, $height, $width, $height);
117 $this->type = $type;
118
119 return $this;
120 }
121
122 /**
123 * Paste over another image.
124 *
125 * Paste a given image source over the current image with an optional position.
126 *
127 * @param string $data Binary data or path to file or another class instance.
128 * @param array $options List of x/y relative offset coords from top left corner. Default: centered.
129 *
130 * @return $this
131 */
132 public function insert($data, $options = array())
133 {
134 $defaults = array(
135 'x' => null,
136 'y' => null,
137 );
138
139 $options = array_merge($defaults, $options);
140
141 switch (true) {
142 case $this->isInstance($data):
143 $image = $this->createFromInstance($data);
144 break;
145
146 case $this->isBinary($data):
147 $image = $this->createFromString($data);
148 break;
149
150 default:
151 $image = $this->createFromFile($data);
152 }
153
154 list($width, $height, $type, $source) = $image;
155
156 $options = $this->calcPosition($options, $width, $height);
157
158 imagecopyresampled($this->resource, $source, $options['x'], $options['y'], 0, 0, $width, $height, $width, $height);
159 imagedestroy($source);
160
161 return $this;
162 }
163
164 /**
165 * Intialise the canvas by width and height.
166 *
167 * @param integer $width Canvas width.
168 * @param integer $height Canvas height.
169 * @param string $options Optional. Background color options. Default: black.
170 *
171 * @return $this
172 */
173 public function canvas($width, $height, $options = array())
174 {
175 $defaults = array(
176 'color' => array(0, 0, 0),
177 'opacity' => 100,
178 );
179
180 $options = array_merge($defaults, $options);
181
182 unset($this->resource);
183
184 $this->resource = imagecreatetruecolor($width, $height);
185
186 // Set the flag to save full alpha channel information
187 imagesavealpha($this->resource, true);
188
189 // Turn off transparency blending (temporarily)
190 imagealphablending($this->resource, false);
191
192 // Get color from options.
193 $color = $this->getColor($options);
194
195 // Completely fill the background with transparent color
196 imagefilledrectangle($this->resource, 0, 0, $width, $height, $color);
197
198 // Restore transparency blending
199 imagealphablending($this->resource, true);
200
201 $this->width = $width;
202 $this->height = $height;
203
204 return $this;
205 }
206
207 /**
208 * Sends HTTP response with current image in given format and quality.
209
210 * @param string $format Optional. File image extension. By default used type from make or insert function.
211 * @param integer $quality Optional. Define optionally the quality of the image. From 0 to 100. Default: 90.
212 *
213 * @return void
214 */
215 public function show($format = null, $quality = 90)
216 {
217 $this->setType($format);
218
219 $quality = $this->getParam($quality, 0, 100);
220
221 switch ($this->type) {
222 case IMAGETYPE_GIF:
223 header('Content-type: image/gif');
224 imagegif($this->resource, null);
225 break;
226
227 case IMAGETYPE_PNG:
228 header('Content-type: image/png');
229 imagepng($this->resource, null, min(floor(10 - $quality / 10), 9));
230 break;
231
232 case IMAGETYPE_WEBP:
233 header('Content-type: image/webp');
234 imagewebp($this->resource, null, $quality);
235 break;
236
237 default:
238 header('Content-type: image/jpeg');
239 imagejpeg($this->resource, null, $quality);
240 break;
241 }
242 }
243
244 /**
245 * Save the image.
246 *
247 * @param string $path Path to the file where to write the image data.
248 * @param integer $quality Optional. Define optionally the quality of the image. From 0 to 100. Default: 90.
249 * @param string $format Optional. File image extension. By default use from path.
250 *
251 * @return $this
252 */
253 public function save($path, $quality = 90, $format = null)
254 {
255 $folder = dirname($path);
256
257 if (!is_writable($folder)) {
258 return $this->handleError('Folder is not writable');
259 }
260
261 if (empty($format)) {
262 $format = pathinfo($path, PATHINFO_EXTENSION);
263 }
264
265 $this->setType($format);
266
267 $quality = $this->getParam($quality, 0, 100);
268
269 switch ($this->type) {
270 case IMAGETYPE_GIF:
271 imagegif($this->resource, $path);
272 break;
273
274 case IMAGETYPE_PNG:
275 imagepng($this->resource, $path, min(floor(10 - $quality / 10), 9));
276 break;
277
278 case IMAGETYPE_WEBP:
279 imagewebp($this->resource, $path, $quality);
280 break;
281
282 default:
283 imagejpeg($this->resource, $path, $quality);
284 }
285 }
286
287 /**
288 * Destroy image resource.
289 *
290 * @return void
291 */
292 public function destroy()
293 {
294 imagedestroy($this->resource);
295 }
296
297 /**
298 * Returns the width in pixels of the current image.
299 *
300 * @return int
301 */
302 public function width()
303 {
304 return $this->width;
305 }
306
307 /**
308 * Returns the height in pixels of the current image.
309 *
310 * @return int
311 */
312 public function height()
313 {
314 return $this->height;
315 }
316
317 /**
318 * Resizes current image based on given width and height.
319 *
320 * @param integer $width Target image width.
321 * @param integer $height Target image height.
322 *
323 * @return $this
324 */
325 public function resize($width, $height)
326 {
327 $this->copyResampled($this->resource, 0, 0, 0, 0, $width, $height, $this->width, $this->height);
328
329 return $this;
330 }
331
332 /**
333 * Upsize image on the largest side.
334 *
335 * @param integer $width Optional. Target image width. By default calculated by ratio.
336 * @param integer $height Optional. Target image height. By default calculated by ratio.
337 *
338 * @return $this
339 */
340 public function upsize($width = null, $height = null)
341 {
342 $ratio = $this->width / $this->height;
343
344 list($width, $height) = $this->calcResizes($width, $height, $ratio);
345
346 if ($width / $height > $ratio) {
347 $height = intval($width / $ratio);
348 } else {
349 $width = intval($height * $ratio);
350 }
351
352 $this->copyResampled($this->resource, 0, 0, 0, 0, $width, $height, $this->width, $this->height);
353
354 return $this;
355 }
356
357 /**
358 * Downside image on the lowest side.
359 *
360 * @param integer $width Optional. Target image width. By default calculated by ratio.
361 * @param integer $height Optional. Target image height. By default calculated by ratio.
362 *
363 * @return $this
364 */
365 public function downsize($width = null, $height = null)
366 {
367 $ratio = $this->width / $this->height;
368
369 list($width, $height) = $this->calcResizes($width, $height, $ratio);
370
371 if ($width / $height > $ratio) {
372 $width = intval($height * $ratio);
373 } else {
374 $height = intval($width / $ratio);
375 }
376
377 $this->copyResampled($this->resource, 0, 0, 0, 0, $width, $height, $this->width, $this->height);
378
379 return $this;
380 }
381
382 /**
383 * Crop an image.
384 *
385 * Cut out a rectangular part of the current image with given width and height.
386 * Define optional x,y coordinates to move the top-left corner of the cutout to a certain position.
387 *
388 * @param integer $width Width of the rectangular cutout.
389 * @param integer $height Height of the rectangular cutout.
390 * @param array $options Optional. List of crop coords. By default crop from center.
391 *
392 * @return $this
393 */
394 public function crop($width, $height, $options = array())
395 {
396 $defaults = array(
397 'x' => null,
398 'y' => null,
399 );
400
401 $options = array_merge($defaults, $options);
402
403 // Update X and Y for nulled arguments.
404 $options = $this->calcPosition($options, $width, $height);
405
406 $this->copyResampled($this->resource, 0, 0, $options['x'], $options['y'], $width, $height, $width, $height);
407
408 return $this;
409 }
410
411 /**
412 * Crop and resize combined.
413 *
414 * Combine cropping and resizing to format image in a smart way.
415 * The method will find the best fitting aspect ratio on the current image automatically,
416 * cut it out and resize it to the given dimension.
417 *
418 * @param integer $width Target image width.
419 * @param integer $height Target image height.
420 * @param string $position Optional. Crop position.
421 *
422 * @return $this
423 */
424 public function fit($width, $height, $position = 'center')
425 {
426 // Resize without upsizing.
427 $this->upsize($width, $height);
428
429 switch ($position) {
430 case 'top-left':
431 $x = 0;
432 $y = 0;
433 break;
434
435 case 'top':
436 $x = intval(($this->width - $width) / 2);
437 $y = 0;
438 break;
439
440 case 'top-right':
441 $x = intval($this->width - $width);
442 $y = 0;
443 break;
444
445 case 'bottom-left':
446 $x = 0;
447 $y = intval($this->height - $height);
448 break;
449
450 case 'bottom':
451 $x = intval(($this->width - $width) / 2);
452 $y = intval($this->height - $height);
453 break;
454
455 case 'bottom-right':
456 $x = intval($this->width - $width);
457 $y = intval($this->height - $height);
458 break;
459
460 case 'right':
461 $x = intval($this->width - $width);
462 $y = intval(($this->height - $height) / 2);
463 break;
464
465 case 'left':
466 $x = 0;
467 $y = intval(($this->height - $height) / 2);
468 break;
469 default:
470 $x = intval(($this->width - $width) / 2);
471 $y = intval(($this->height - $height) / 2);
472 }
473
474 $this->crop($width, $height, compact('x', 'y'));
475
476 return $this;
477 }
478
479 /**
480 * Draw a line from x,y point 1 to x,y point 2 on current image.
481 *
482 * @param integer $x1 X-Coordinate of the starting point.
483 * @param integer $y1 Y-Coordinate of the starting point.
484 * @param integer $x2 X-Coordinate of the end point.
485 * @param integer $y2 Y-Coordinate of the end point.
486 * @param array $options Optional. List of line options.
487 *
488 * @return $this
489 */
490 public function line($x1, $y1, $x2, $y2, $options = array())
491 {
492 $defaults = array(
493 'color' => array(0, 0, 0),
494 'opacity' => 0,
495 'width' => 1,
496 );
497
498 $options = array_merge($defaults, $options);
499
500 // Get color from options.
501 $color = $this->getColor($options);
502
503 imagesetthickness($this->resource, $options['width']);
504
505 // Draw new line.
506 imageline($this->resource, $x1, $y1, $x2, $y2, $color);
507
508 imagesetthickness($this->resource, 1);
509
510 return $this;
511 }
512
513 /**
514 * Draw a colored rectangle on current image.
515 *
516 * @param integer $x X-Coordinate of the starting point.
517 * @param integer $y Y-Coordinate of the starting point.
518 * @param integer $width Width in pixels.
519 * @param integer $height Height in pixels.
520 * @param array $options Optional. List of line options.
521 *
522 * @return $this
523 */
524 public function rectangle($x, $y, $width, $height, $options = array())
525 {
526 $defaults = array(
527 'color' => array(0, 0, 0),
528 'opacity' => 0,
529 'thickness' => 1,
530 'outline' => false,
531 );
532
533 $options = array_merge($defaults, $options);
534
535 // Get color from options.
536 $color = $this->getColor($options);
537
538 imagesetthickness($this->resource, $options['thickness']);
539
540 if (false === $options['outline']) {
541 imagefilledrectangle($this->resource, $x, $y, $x + $width, $y + $height, $color);
542 } else {
543 imagerectangle($this->resource, $x, $y, $x + $width, $y + $height, $color);
544 }
545
546 imagesetthickness($this->resource, 1);
547
548 return $this;
549 }
550
551 /**
552 * Draw an ellipse.
553 *
554 * @param integer $x X-Coordinate of the center point.
555 * @param integer $y Y-Coordinate of the center point.
556 * @param integer $width Width in pixels.
557 * @param integer $height Height in pixels.
558 * @param array $options Optional. List of line options.
559 *
560 * @return $this
561 */
562 public function ellipse($x, $y, $width, $height, $options = array())
563 {
564 $defaults = array(
565 'color' => array(0, 0, 0),
566 'opacity' => 0,
567 'outline' => false,
568 );
569
570 $options = array_merge($defaults, $options);
571
572 // Get color from options.
573 $color = $this->getColor($options);
574
575 if (true === $options['outline']) {
576 imageellipse($this->resource, $x, $y, $width, $height, $color);
577 } else {
578 imagefilledellipse($this->resource, $x, $y, $width, $height, $color);
579 }
580
581 return $this;
582 }
583
584 /**
585 * Change the brightness of the current image by the given level.
586 * Use values between -100 for min. brightness 0 for no change and +100 for max.
587 *
588 * @param integer $level Optional. The level of brightness. Default: 0.
589 *
590 * @return $this
591 */
592 public function brightness($level = 0)
593 {
594 $level = $this->getParam($level, -100, 100);
595
596 imagefilter($this->resource, IMG_FILTER_BRIGHTNESS, $level * 2.55);
597
598 return $this;
599 }
600
601 /**
602 * Change the contrast of the current image by the given level.
603 * Use values between -100 for min contrast 0 for no change and +100 for max.
604 *
605 * @param integer $level Optional. The level of contrast. Default: 0.
606 *
607 * @return $this
608 */
609 public function contrast($level = 0)
610 {
611 $level = $this->getParam($level, -100, 100);
612
613 imagefilter($this->resource, IMG_FILTER_CONTRAST, $level);
614
615 return $this;
616 }
617
618 /**
619 * Turn an image into a grayscale version.
620 *
621 * @return $this
622 */
623 public function grayscale()
624 {
625 imagefilter($this->resource, IMG_FILTER_GRAYSCALE);
626
627 return $this;
628 }
629
630 /**
631 * Apply a blur image effect.
632 *
633 * Original version from Martijn Frazer based on
634 * https://stackoverflow.com/a/20264482
635 *
636 * @return $this
637 */
638 public function blur()
639 {
640 $width = $this->width;
641 $height = $this->height;
642
643 // Scale by 25% and apply Gaussian blur.
644 $this->resize($width / 4, $height / 4);
645 imagefilter($this->resource, IMG_FILTER_GAUSSIAN_BLUR);
646
647 // Scale result by 200% and blur again.
648 $this->resize($width / 2, $height / 2);
649 imagefilter($this->resource, IMG_FILTER_GAUSSIAN_BLUR);
650
651 // Scale result back to original size and blur one more time.
652 $this->resize($width, $height);
653 imagefilter($this->resource, IMG_FILTER_GAUSSIAN_BLUR);
654
655 return $this;
656 }
657
658 /**
659 * Invert colors of an image.
660 *
661 * @return $this
662 */
663 public function invert()
664 {
665 imagefilter($this->resource, IMG_FILTER_NEGATE);
666
667 return $this;
668 }
669
670 /**
671 * Draw black opactity rectangle on image.
672 *
673 * @param integer $level Optional. Blackout level. Default: 0.
674 *
675 * @return $this
676 */
677 public function blackout($level = 0)
678 {
679 $level = $this->getParam($level, 0, 100);
680
681 $this->rectangle(
682 0, 0, $this->width, $this->height,
683 array(
684 'color' => '#000',
685 'opacity' => 100 - $level,
686 )
687 );
688
689 return $this;
690 }
691
692 /**
693 * Rotate image.
694 *
695 * @param float $angle Rotation angle.
696 * @param int $options Optional. Optional. List of rotation options.
697 *
698 * @return $this
699 */
700 public function rotate($angle, $options = array())
701 {
702 $defaults = array(
703 'color' => array(0, 0, 0),
704 'opacity' => 100,
705 );
706
707 $options = array_merge($defaults, $options);
708
709 // Get color from options.
710 $color = $this->getColor($options);
711
712 $this->resource = imagerotate($this->resource, $angle, $color);
713
714 $this->width = imagesx($this->resource);
715 $this->height = imagesy($this->resource);
716
717 return $this;
718 }
719
720 /**
721 * Draw text on image.
722 *
723 * @param string $text Text strings. Multiline availible.
724 * @param array $options Optional. List of text settings.
725 * @param array $boundary Optional. Actual dimensions of the drawn text box.
726 *
727 * @return $this
728 */
729 public function text($text, $options = array(), &$boundary = array())
730 {
731 $defaults = array(
732 'x' => 0,
733 'y' => 0,
734 'width' => null,
735 'height' => null,
736 'fontsize' => 48,
737 'color' => array(0, 0, 0),
738 'lineheight' => 1.5,
739 'opacity' => 1,
740 'horizontal' => 'left',
741 'vertical' => 'top',
742 'fontpath' => null,
743 'debug' => false,
744 );
745
746 $options = array_merge($defaults, $options);
747
748 if (!is_readable($options['fontpath'])) {
749 $this->handleError('Font is not a valid file');
750 }
751
752 // Set default width if undefined
753 if (null === $options['width']) {
754 $options['width'] = $this->width - $options['x'];
755 }
756
757 // Set default height if undefined
758 if (null === $options['height']) {
759 $options['height'] = $this->height - $options['y'];
760 }
761
762 // Draw debug rectangle.
763 if (true === $options['debug']) {
764 $this->drawDebug($options);
765 }
766
767 // Get color from options.
768 $color = $this->getColor($options);
769
770 // Get wrapped text and updated font-size.
771 $text = $this->wrapText($text, $options);
772
773 // Get text lines as array.
774 $lines = explode("\n", $text);
775
776 // Set default boundary vaules.
777 $boundary = array_merge(array('width' => 0, 'height' => 0));
778
779 foreach ($lines as $index => $line) {
780 list($x, $y, $width, $height) = $this->getOffset($options, $lines, $index);
781
782 // Draw text line.
783 imagefttext($this->resource, $options['fontsize'], 0, $x, $y, $color, $options['fontpath'], $line);
784
785 $boundary = array(
786 'width' => max($width, $boundary['width']),
787 'height' => $boundary['height'] + $height,
788 );
789 }
790
791 return $this;
792 }
793
794 /**
795 * Wrap text to box and update font-size if necessary.
796 *
797 * @param string $text Text to draw.
798 * @param array $options List of text options.
799 *
800 * @return string
801 */
802 protected function wrapText($text, &$options)
803 {
804 do {
805 $wrapped = $this->addBreaklines($text, $options);
806
807 // Get lines from wrapped text.
808 $lines = explode("\n", $wrapped);
809
810 // Get text width.
811 $width = $this->getTextWidth($wrapped, $options);
812
813 // Sum of all lines heights.
814 $height = $options['fontsize'] * $options['lineheight'] * count($lines);
815
816 if ($width <= $options['width'] && $height <= $options['height']) {
817 break;
818 }
819
820 $options['fontsize'] = $options['fontsize'] - 1;
821 } while ($options['fontsize'] > 0);
822
823 return $wrapped;
824 }
825
826 /**
827 * Calculates text width.
828 *
829 * @param string $text Text to draw.
830 * @param array $options List of text options.
831 *
832 * @return int
833 */
834 protected function getTextWidth($text, $options)
835 {
836 $box = imageftbbox($options['fontsize'], 0, $options['fontpath'], $text);
837
838 return $box[2];
839 }
840
841 /**
842 * Add break line to text according font settings.
843 *
844 * @param string $text Text to draw.
845 * @param array $options Optional. List of image options.
846 * @param string $output Optional. Non-breaklined output.
847 *
848 * @return string
849 */
850 protected function addBreaklines($text, $options, $output = '')
851 {
852 $line = '';
853
854 // Split text to words.
855 $words = explode(' ', $text);
856
857 foreach ($words as $word) {
858 $sentence = $line . ' ' . $word;
859
860 if (empty($line)) {
861 $sentence = $word;
862 }
863
864 $box = imageftbbox($options['fontsize'], 0, $options['fontpath'], $sentence);
865
866 // Add new line to output.
867 if ($box[2] > $options['width']) {
868 $output = $output . $line . "\n";
869
870 // Reset line.
871 $line = $word;
872 continue;
873 }
874
875 $line = $sentence;
876 }
877
878 // Add last line to output.
879 $output = $output . $line;
880
881 return $output;
882 }
883
884 /**
885 * Get color from options array using opacity.
886 *
887 * @param array $options List of image options.
888 *
889 * @return integer
890 */
891 protected function getColor($options)
892 {
893 $rgb = $options['color'];
894
895 if (is_string($rgb)) {
896 $rgb = array_map(
897 function ($c) {
898 return hexdec(str_pad($c, 2, $c));
899 },
900 str_split(ltrim($rgb, '#'), strlen($rgb) > 4 ? 2 : 1)
901 );
902 }
903
904 $opacity = $options['opacity'] / 100 * 127;
905
906 // Create image color width opacity.
907 return imagecolorallocatealpha($this->resource, $rgb[0], $rgb[1], $rgb[2], $opacity);
908 }
909
910 /**
911 * Get param using min max values.
912 *
913 * @param integer $value Initial value.
914 * @param integer $min Minimulm value.
915 * @param integer $max Maximum value.
916 *
917 * @return integer
918 */
919 protected function getParam($value, $min, $max)
920 {
921 $value = (int) $value;
922
923 return max(min($value, $max), $min);
924 }
925
926 /**
927 * Get offset for text to draw.
928 *
929 * @param integer $options List of image options.
930 * @param array $lines List of text lines.
931 * @param integer $index Current line index in the loop.
932 *
933 * @return array
934 */
935 protected function getOffset($options, $lines, $index)
936 {
937 $box = imageftbbox($options['fontsize'], 0, $options['fontpath'], $lines[$index]);
938
939 $width = abs($box[6] - $box[4]);
940 $height = $options['fontsize'] * $options['lineheight'];
941
942 // Smart offset for the first line respecting line height.
943 $offset = $options['fontsize'] + (($height - $options['fontsize']) / 2);
944
945 $x = $options['x'];
946 $y = $options['y'] + $offset + $index * $height;
947
948 if (0 === $index) {
949 $y = $options['y'] + $offset;
950 }
951
952 switch ($options['horizontal']) {
953 case 'center':
954 $x = $x + (($options['width'] - $width) / 2);
955 break;
956
957 case 'right':
958 $x = $x + ($options['width'] - $width);
959 break;
960 }
961
962 switch ($options['vertical']) {
963 case 'center':
964 $y = $y + (($options['height'] - ($height * count($lines))) / 2);
965 break;
966
967 case 'bottom':
968 $y = $y + ($options['height'] - ($height * count($lines)));
969 break;
970 }
971
972 return array($x, $y, $width, $height);
973 }
974
975 /**
976 * Draw debug box for text by options.
977 *
978 * @param array $options List of text options.
979 *
980 * @return void
981 */
982 protected function drawDebug($options)
983 {
984 $styles = array(
985 'color' => array(rand(150, 255), rand(150, 255), rand(150, 255)),
986 'opacity' => 50,
987 );
988
989 $this->rectangle($options['x'], $options['y'], $options['width'], $options['height'], $styles);
990 }
991
992 /**
993 * Set output image type using file format.
994 *
995 * @param string $format File format extension. Can be jpg, gif or png.
996 *
997 * @return void
998 */
999 protected function setType($format)
1000 {
1001 $format = strtolower($format);
1002
1003 switch ($format) {
1004 case 'gif':
1005 $this->type = IMAGETYPE_GIF;
1006 break;
1007
1008 case 'png':
1009 $this->type = IMAGETYPE_PNG;
1010 break;
1011
1012 case 'webp':
1013 $this->type = IMAGETYPE_WEBP;
1014 break;
1015
1016 case 'jpg':
1017 $this->type = IMAGETYPE_JPEG;
1018 break;
1019 }
1020 }
1021
1022 /**
1023 * Create image using file path.
1024 *
1025 * @param string $file Path to image file.
1026 *
1027 * @return array
1028 */
1029 protected function createFromFile($file)
1030 {
1031 list($width, $height, $type) = getimagesize($file);
1032
1033 $source = $this->getSource($file, $type);
1034
1035 return array($width, $height, $type, $source);
1036 }
1037
1038 /**
1039 * Create a new image from the image stream in the string.
1040 *
1041 * @param string $data A string containing the image data.
1042 *
1043 * @return array
1044 */
1045 protected function createFromString($data)
1046 {
1047 // Get image dimensions.
1048 list($width, $height, $type) = getimagesizefromstring($data);
1049
1050 $source = imagecreatefromstring($data);
1051
1052 return array($width, $height, $type, $source);
1053 }
1054
1055 /**
1056 * Get image data from instance.
1057 *
1058 * @param string $instance Instance of PosterEditor class.
1059 *
1060 * @return array
1061 */
1062 protected function createFromInstance($instance)
1063 {
1064 return array($instance->width, $instance->height, $instance->type, $instance->resource);
1065 }
1066
1067 /**
1068 * Get image source using file.
1069 *
1070 * @param string $file Image file.
1071 * @param int $type File type.
1072 *
1073 * @return instance
1074 */
1075 protected function getSource($file, $type)
1076 {
1077 switch ($type) {
1078 case IMAGETYPE_GIF:
1079 $source = imagecreatefromgif($file);
1080 break;
1081
1082 case IMAGETYPE_JPEG:
1083 $source = imagecreatefromjpeg($file);
1084 break;
1085
1086 case IMAGETYPE_PNG:
1087 $source = imagecreatefrompng($file);
1088 break;
1089
1090 case IMAGETYPE_WEBP:
1091 $source = imagecreatefromwebp($file);
1092
1093 default:
1094 return $this->handleError('Unsupported image type');
1095 }
1096
1097 return $source;
1098 }
1099
1100 /**
1101 * Find image center usin from and to values.
1102 *
1103 * @param integer $from Source size.
1104 * @param integer $to Destination size.
1105 *
1106 * @return integer
1107 */
1108 protected function findCenter($from, $to)
1109 {
1110 return ceil(($from - $to) * 0.5);
1111 }
1112
1113 /**
1114 * Calculate new width and height values for resize.
1115 *
1116 * @param integer $width Current image width.
1117 * @param integer $height Current image height.
1118 * @param float $ratio Width to height relation.
1119 *
1120 * @return array
1121 */
1122 protected function calcResizes($width, $height, $ratio)
1123 {
1124 if (null === $width) {
1125 $width = $this->width;
1126
1127 // Try to calc new width by ratio.
1128 if (null !== $height) {
1129 $width = $height * $ratio;
1130 }
1131 }
1132
1133 if (null === $height) {
1134 $height = $this->height;
1135
1136 // Try to calc new height by ratio.
1137 if (null !== $width) {
1138 $height = $width / $ratio;
1139 }
1140 }
1141
1142 return array($width, $height);
1143 }
1144
1145 /**
1146 * Update position options for nulled x/y arguments.
1147 *
1148 * @param array $options Position options.
1149 * @param int $width Calculated image width.
1150 * @param int $height Calculated image height.
1151 *
1152 * @return array
1153 */
1154 protected function calcPosition($options, $width, $height)
1155 {
1156 if (null === $options['x']) {
1157 $options['x'] = $this->findCenter($this->width, $width);
1158 }
1159
1160 if (null === $options['y']) {
1161 $options['y'] = $this->findCenter($this->height, $height);
1162 }
1163
1164 return $options;
1165 }
1166
1167 /**
1168 * Helper function to copy and resize part of an image with resampling.
1169 *
1170 * @param resource $source Source image resource.
1171 * @param int $dx X-coordinate of destination point.
1172 * @param int $dy Y-coordinate of destination point.
1173 * @param int $sx X-coordinate of source point.
1174 * @param int $sy Y-coordinate of source point.
1175 * @param int $dw Destination width.
1176 * @param int $dh Destination height.
1177 * @param int $sw Source width.
1178 * @param int $sh Source height.
1179 *
1180 * @return $this
1181 */
1182 protected function copyResampled($source, $dx, $dy, $sx, $sy, $dw, $dh, $sw, $sh)
1183 {
1184 $this->canvas($dw, $dh);
1185
1186 imagecopyresampled($this->resource, $source, $dx, $dy, $sx, $sy, $dw, $dh, $sw, $sh);
1187 imagedestroy($source);
1188
1189 return $this;
1190 }
1191
1192 /**
1193 * Determines if source data is binary data.
1194 *
1195 * @param string $data File binary data.
1196 *
1197 * @return boolean
1198 */
1199 protected function isBinary($data)
1200 {
1201 if (is_string($data)) {
1202 $mime = finfo_buffer(finfo_open(FILEINFO_MIME_TYPE), $data);
1203 return (substr($mime, 0, 4) != 'text' && $mime != 'application/x-empty');
1204 }
1205
1206 return false;
1207 }
1208
1209 /**
1210 * Determines if source data is instance of current class.
1211 *
1212 * @param string $insance Instance of class.
1213 *
1214 * @return boolean
1215 */
1216 protected function isInstance($insance)
1217 {
1218 if ($insance instanceof PosterEditor) {
1219 return true;
1220 }
1221
1222 return false;
1223 }
1224
1225 /**
1226 * Handle errors
1227 *
1228 * @param string $error Error message.
1229 *
1230 * @return Exception
1231 */
1232 protected function handleError($error)
1233 {
1234 throw new Exception($error);
1235 }
1236 }
1237