Cyclone Slider - Version 2.12.0

Version Description

  • 2016-07-06 =
  • Now uses Grafika for image editing.
  • Added link in slide preview image that opens in new tab when clicked.
Download this release

Release Info

Developer kosinix
Plugin Icon 128x128 Cyclone Slider
Version 2.12.0
Comparing to
See all releases

Code changes from version 2.11.0 to 2.12.0

Files changed (41) hide show
  1. README.txt +6 -2
  2. cyclone-slider.php +2 -10
  3. js/admin.js +6 -6
  4. src/CycloneSlider/Admin.php +1 -0
  5. src/CycloneSlider/Data.php +19 -8
  6. src/CycloneSlider/Frontend.php +3 -2
  7. src/CycloneSlider/Grafika/Color.php +105 -0
  8. src/CycloneSlider/Grafika/DrawingObject/CubicBezier.php +103 -0
  9. src/CycloneSlider/Grafika/DrawingObject/Ellipse.php +115 -0
  10. src/CycloneSlider/Grafika/DrawingObject/Line.php +83 -0
  11. src/CycloneSlider/Grafika/DrawingObject/Polygon.php +108 -0
  12. src/CycloneSlider/Grafika/DrawingObject/QuadraticBezier.php +86 -0
  13. src/CycloneSlider/Grafika/DrawingObject/Rectangle.php +112 -0
  14. src/CycloneSlider/Grafika/DrawingObjectInterface.php +17 -0
  15. src/CycloneSlider/Grafika/EditorInterface.php +348 -0
  16. src/CycloneSlider/Grafika/EffectInterface.php +17 -0
  17. src/CycloneSlider/Grafika/Gd/DrawingObject/CubicBezier.php +412 -0
  18. src/CycloneSlider/Grafika/Gd/DrawingObject/Ellipse.php +42 -0
  19. src/CycloneSlider/Grafika/Gd/DrawingObject/Line.php +36 -0
  20. src/CycloneSlider/Grafika/Gd/DrawingObject/Polygon.php +64 -0
  21. src/CycloneSlider/Grafika/Gd/DrawingObject/QuadraticBezier.php +208 -0
  22. src/CycloneSlider/Grafika/Gd/DrawingObject/Rectangle.php +36 -0
  23. src/CycloneSlider/Grafika/Gd/Editor.php +1115 -0
  24. src/CycloneSlider/Grafika/Gd/Effect/Dither.php +100 -0
  25. src/CycloneSlider/Grafika/Gd/Image.php +285 -0
  26. src/CycloneSlider/Grafika/Grafika.php +105 -0
  27. src/CycloneSlider/Grafika/ImageInterface.php +49 -0
  28. src/CycloneSlider/Grafika/ImageType.php +21 -0
  29. src/CycloneSlider/Grafika/Imagick/DrawingObject/CubicBezier.php +51 -0
  30. src/CycloneSlider/Grafika/Imagick/DrawingObject/Ellipse.php +40 -0
  31. src/CycloneSlider/Grafika/Imagick/DrawingObject/Line.php +41 -0
  32. src/CycloneSlider/Grafika/Imagick/DrawingObject/Polygon.php +48 -0
  33. src/CycloneSlider/Grafika/Imagick/DrawingObject/QuadraticBezier.php +55 -0
  34. src/CycloneSlider/Grafika/Imagick/DrawingObject/Rectangle.php +41 -0
  35. src/CycloneSlider/Grafika/Imagick/Editor.php +975 -0
  36. src/CycloneSlider/Grafika/Imagick/Effect/Dither.php +91 -0
  37. src/CycloneSlider/Grafika/Imagick/Image.php +135 -0
  38. src/CycloneSlider/ImageResizer.php +33 -12
  39. src/autoloader.php +11 -0
  40. views/slide-edit.php +1 -1
  41. views/slider-advanced-settings.php +14 -5
README.txt CHANGED
@@ -3,7 +3,7 @@ Contributors: kosinix
3
  Donate link: http://www.codefleet.net/donate/
4
  Tags: slider, slideshow, drag-and-drop, wordpress-slider, wordpress-slideshow, cycle 2, jquery, responsive, translation-ready, custom-post, cyclone-slider
5
  Requires at least: 3.5
6
- Tested up to: 4.4.2
7
  Stable tag: trunk
8
  License: GPLv3
9
  License URI: http://www.gnu.org/licenses/gpl-3.0.html
@@ -117,7 +117,11 @@ Inside `wp-content` create a folder named "cycloneslider". Add your templates in
117
 
118
  == Changelog ==
119
 
120
- = 2.11.0 - 2015-03-30 =
 
 
 
 
121
  * New. Templates can now be added using a sub-plugin. See [docs](http://docs.codefleet.net/cyclone-slider-2/creating-your-own-template/).
122
  * New filter cycloneslider_view_vars. See [docs for a full list of filters](http://docs.codefleet.net/cyclone-slider-2/filters/).
123
  * Change overall textdomain handling to be compatible with wordpress.org translation.
3
  Donate link: http://www.codefleet.net/donate/
4
  Tags: slider, slideshow, drag-and-drop, wordpress-slider, wordpress-slideshow, cycle 2, jquery, responsive, translation-ready, custom-post, cyclone-slider
5
  Requires at least: 3.5
6
+ Tested up to: 4.5.3
7
  Stable tag: trunk
8
  License: GPLv3
9
  License URI: http://www.gnu.org/licenses/gpl-3.0.html
117
 
118
  == Changelog ==
119
 
120
+ = 2.12.0 - 2016-07-06 =
121
+ * Now uses [Grafika](http://kosinix.github.io/grafika/) for image editing.
122
+ * Added link in slide preview image that opens in new tab when clicked.
123
+
124
+ = 2.11.0 - 2016-03-30 =
125
  * New. Templates can now be added using a sub-plugin. See [docs](http://docs.codefleet.net/cyclone-slider-2/creating-your-own-template/).
126
  * New filter cycloneslider_view_vars. See [docs for a full list of filters](http://docs.codefleet.net/cyclone-slider-2/filters/).
127
  * Change overall textdomain handling to be compatible with wordpress.org translation.
cyclone-slider.php CHANGED
@@ -3,7 +3,7 @@
3
  Plugin Name: Cyclone Slider 2
4
  Plugin URI: http://www.codefleet.net/cyclone-slider-2/
5
  Description: Create and manage sliders with ease. Built for both casual users and developers.
6
- Version: 2.11.0
7
  Author: Nico Amarilla
8
  Author URI: http://www.codefleet.net/
9
  License: GPLv3
@@ -12,15 +12,7 @@ Domain Path: /languages
12
  Text Domain: cyclone-slider-2
13
  */
14
 
15
- // Autoloader
16
- function cycloneslider_autoloader( $class_name ) {
17
- if( false !== strpos( $class_name, 'CycloneSlider' ) ){
18
- $classes_dir = realpath( plugin_dir_path( __FILE__ ) ) . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR;
19
- $class_file = str_replace( '_', DIRECTORY_SEPARATOR, $class_name ) . '.php';
20
- require_once $classes_dir . $class_file;
21
- }
22
- }
23
- spl_autoload_register('cycloneslider_autoloader');
24
 
25
  $cyclone_slider_plugin_instance = null;
26
  $cyclone_slider_saved_done = false;
3
  Plugin Name: Cyclone Slider 2
4
  Plugin URI: http://www.codefleet.net/cyclone-slider-2/
5
  Description: Create and manage sliders with ease. Built for both casual users and developers.
6
+ Version: 2.12.0
7
  Author: Nico Amarilla
8
  Author URI: http://www.codefleet.net/
9
  License: GPLv3
12
  Text Domain: cyclone-slider-2
13
  */
14
 
15
+ require_once 'src/autoloader.php';
 
 
 
 
 
 
 
 
16
 
17
  $cyclone_slider_plugin_instance = null;
18
  $cyclone_slider_saved_done = false;
js/admin.js CHANGED
@@ -151,14 +151,14 @@ jQuery(document).ready(function($){
151
  });
152
 
153
  /*** Add image to slide ***/
154
- $('#cyclone-slides-metabox').on('wpAddImage', '.cs-media-gallery-show', function(e, image_url, attachment_id){
155
  var current_slide_box, slide_thumb, slide_attachment_id;
156
 
157
  current_slide_box = $(this).parents('.cs-slide');/*** Get current box ***/
158
  slide_thumb = current_slide_box.find('.cs-image-thumb');/*** Find the thumb ***/
159
  slide_attachment_id = current_slide_box.find('.cs-image-id ');/*** Find the hidden field that will hold the attachment id ***/
160
 
161
- slide_thumb.html('<img src="'+image_url+'" alt="thumb">').show();
162
  slide_attachment_id.val(attachment_id);
163
 
164
  });
@@ -166,12 +166,12 @@ jQuery(document).ready(function($){
166
  /*** Add multiple images as slide ***/
167
  $('#cyclone-slides-metabox').on('wpAddImages', '.cs-multiple-slides', function(e, media_attachments){
168
  var cur_slide_count = $('.cs-sortables .cs-slide').length;
169
-
170
  for(i=0; i<media_attachments.length; ++i){
171
 
172
  $('#cyclone-slides-metabox .cs-add-slide').trigger('click');
173
 
174
- $('.cs-sortables .cs-slide').eq(cur_slide_count+i).find('.cs-media-gallery-show').trigger('wpAddImage', [media_attachments[i].url, media_attachments[i].id]);
175
  }
176
 
177
  });
@@ -459,8 +459,8 @@ jQuery(document).ready(function($){
459
  } else {
460
  img_url = media_attachment.sizes.medium.url;
461
  }
462
-
463
- triggering_element.trigger('wpAddImage', [img_url, media_attachment.id]);
464
  });
465
 
466
  // Now that everything has been set, let's open up the frame.
151
  });
152
 
153
  /*** Add image to slide ***/
154
+ $('#cyclone-slides-metabox').on('wpAddImage', '.cs-media-gallery-show', function(e, image_url, attachment_id, media_attachment){
155
  var current_slide_box, slide_thumb, slide_attachment_id;
156
 
157
  current_slide_box = $(this).parents('.cs-slide');/*** Get current box ***/
158
  slide_thumb = current_slide_box.find('.cs-image-thumb');/*** Find the thumb ***/
159
  slide_attachment_id = current_slide_box.find('.cs-image-id ');/*** Find the hidden field that will hold the attachment id ***/
160
 
161
+ slide_thumb.html('<a target="_blank" href="'+media_attachment.url+'"><img src="'+image_url+'" alt="thumb"></a>').show();
162
  slide_attachment_id.val(attachment_id);
163
 
164
  });
166
  /*** Add multiple images as slide ***/
167
  $('#cyclone-slides-metabox').on('wpAddImages', '.cs-multiple-slides', function(e, media_attachments){
168
  var cur_slide_count = $('.cs-sortables .cs-slide').length;
169
+
170
  for(i=0; i<media_attachments.length; ++i){
171
 
172
  $('#cyclone-slides-metabox .cs-add-slide').trigger('click');
173
 
174
+ $('.cs-sortables .cs-slide').eq(cur_slide_count+i).find('.cs-media-gallery-show').trigger('wpAddImage', [media_attachments[i].url, media_attachments[i].id, media_attachments[i]]);
175
  }
176
 
177
  });
459
  } else {
460
  img_url = media_attachment.sizes.medium.url;
461
  }
462
+
463
+ triggering_element.trigger('wpAddImage', [img_url, media_attachment.id, media_attachment]);
464
  });
465
 
466
  // Now that everything has been set, let's open up the frame.
src/CycloneSlider/Admin.php CHANGED
@@ -334,6 +334,7 @@ class CycloneSlider_Admin {
334
  $vars['slider_settings'] = $slider_settings;
335
  $vars['slide'] = $slide;
336
  $vars['image_url'] = $image_url;
 
337
  $vars['box_title'] = $box_title;
338
  $vars['debug'] = ($this->debug) ? cyclone_slider_debug($slide) : '';
339
  $vars['effects'] = $this->data->get_slide_effects();
334
  $vars['slider_settings'] = $slider_settings;
335
  $vars['slide'] = $slide;
336
  $vars['image_url'] = $image_url;
337
+ $vars['full_image_url'] = wp_get_attachment_url($slide['id']);
338
  $vars['box_title'] = $box_title;
339
  $vars['debug'] = ($this->debug) ? cyclone_slider_debug($slide) : '';
340
  $vars['effects'] = $this->data->get_slide_effects();
src/CycloneSlider/Data.php CHANGED
@@ -150,7 +150,7 @@ class CycloneSlider_Data {
150
  $settings_to_save['width_management'] = sanitize_text_field( $settings['width_management'] );
151
  // Pro options
152
  $settings_to_save['easing'] = '';
153
- $settings_to_save['resize_option'] = 'auto';
154
  $settings_to_save['allow_wrap'] = 'true';
155
  $settings_to_save['dynamic_height'] = 'off';
156
  $settings_to_save['dynamic_height_speed'] = 250;
@@ -801,13 +801,24 @@ class CycloneSlider_Data {
801
  * @return array The array of resize options in a key-value pair
802
  */
803
  public function get_resize_options(){
804
- return array(
805
- 'auto' => 'Auto',
806
- 'crop' => 'Crop',
807
- 'exact' => 'Exact',
808
- 'landscape' => 'Landscape',
809
- 'portrait' => 'Portrait'
810
- );
 
 
 
 
 
 
 
 
 
 
 
811
  }
812
 
813
  /**
150
  $settings_to_save['width_management'] = sanitize_text_field( $settings['width_management'] );
151
  // Pro options
152
  $settings_to_save['easing'] = '';
153
+ $settings_to_save['resize_option'] = 'fit';
154
  $settings_to_save['allow_wrap'] = 'true';
155
  $settings_to_save['dynamic_height'] = 'off';
156
  $settings_to_save['dynamic_height_speed'] = 250;
801
  * @return array The array of resize options in a key-value pair
802
  */
803
  public function get_resize_options(){
804
+ if(version_compare(PHP_VERSION, '5.3', '>=')) { // 5.3+
805
+ return array(
806
+ 'fit' => 'Fit',
807
+ 'fill' => 'Fill',
808
+ 'crop' => 'Crop',
809
+ 'exact' => 'Exact',
810
+ 'exactWidth' => 'Exact Width',
811
+ 'exactHeight' => 'Exact Height'
812
+ );
813
+ } else { // 5.2
814
+ return array(
815
+ 'auto' => 'Auto',
816
+ 'crop' => 'Crop',
817
+ 'exact' => 'Exact',
818
+ 'landscape' => 'Landscape',
819
+ 'portrait' => 'Portrait'
820
+ );
821
+ }
822
  }
823
 
824
  /**
src/CycloneSlider/Frontend.php CHANGED
@@ -15,7 +15,7 @@ class CycloneSlider_Frontend {
15
  /**
16
  * @var CycloneSlider_Vimeo
17
  */
18
- protected $vimeo;
19
  /**
20
  * @var CycloneSlider_View
21
  */
@@ -218,7 +218,8 @@ class CycloneSlider_Frontend {
218
 
219
  $this->view->set_view_folder( dirname( $view_file ) ); // Set to template folder
220
 
221
- $vars = apply_filters('cycloneslider_view_vars', $vars);
 
222
  $slider_html = $this->view->get_render( basename( $view_file ), $vars );
223
 
224
  $this->view->set_view_folder( $current_view_folder ); // Restore
15
  /**
16
  * @var CycloneSlider_Vimeo
17
  */
18
+ public $vimeo;
19
  /**
20
  * @var CycloneSlider_View
21
  */
218
 
219
  $this->view->set_view_folder( dirname( $view_file ) ); // Set to template folder
220
 
221
+ $vars = apply_filters('cycloneslider_view_vars', $vars, $this);
222
+
223
  $slider_html = $this->view->get_render( basename( $view_file ), $vars );
224
 
225
  $this->view->set_view_folder( $current_view_folder ); // Restore
src/CycloneSlider/Grafika/Color.php ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace CycloneSlider\Grafika;
3
+
4
+ /**
5
+ * Class Color
6
+ * @package Grafika
7
+ */
8
+ class Color {
9
+
10
+ /**
11
+ * @var string Hex string: #FFFFFF
12
+ */
13
+ protected $hexString;
14
+
15
+ /**
16
+ * @var float Transparency value 0-1
17
+ */
18
+ protected $alpha;
19
+
20
+ /**
21
+ * Color constructor.
22
+ *
23
+ * @param string $hexString Hex string
24
+ * @param float $alpha Transparency value 0-1
25
+ */
26
+ public function __construct( $hexString = '', $alpha = 1.0 ){
27
+ $this->hexString = $hexString; // TODO: Validate hexstring
28
+ $this->alpha = $alpha;
29
+ }
30
+
31
+ /**
32
+ * Get RGB array
33
+ *
34
+ * @return array Contains array($r, $g, $b)
35
+ */
36
+ public function getRgb(){
37
+ return $this->hexToRgb( $this->hexString );
38
+ }
39
+
40
+ /**
41
+ * Get RGBA array
42
+ *
43
+ * @return array Contains array($r, $g, $b, $a)
44
+ */
45
+ public function getRgba(){
46
+ $rgba = $this->hexToRgb( $this->hexString );
47
+ $rgba[] = $this->alpha;
48
+ return $rgba;
49
+ }
50
+
51
+ /**
52
+ * Convert hex string to RGB
53
+ * @param string $hex Hex string. Possible values: #ffffff, #fff, fff
54
+ * @return array Contains (RGB) values red, green and blue
55
+ */
56
+ public function hexToRgb( $hex ) {
57
+ $hex = ltrim($hex, '#'); // Remove #
58
+
59
+ if(strlen($hex) == 3) {
60
+ $r = hexdec(substr($hex,0,1).substr($hex,0,1));
61
+ $g = hexdec(substr($hex,1,1).substr($hex,1,1));
62
+ $b = hexdec(substr($hex,2,1).substr($hex,2,1));
63
+ } else {
64
+ $r = hexdec(substr($hex,0,2));
65
+ $g = hexdec(substr($hex,2,2));
66
+ $b = hexdec(substr($hex,4,2));
67
+ }
68
+ return array($r, $g, $b); // Returns an array with the rgb values
69
+ }
70
+
71
+ /**
72
+ * Get hex string.
73
+ *
74
+ * @return string
75
+ */
76
+ public function getHexString() {
77
+ return $this->hexString;
78
+ }
79
+
80
+ /**
81
+ * Set hex string.
82
+ *
83
+ * @param string $hexString
84
+ */
85
+ public function setHexString($hexString) {
86
+ $this->hexString = $hexString;
87
+ }
88
+
89
+ /**
90
+ * Alpha value.
91
+ * @return float
92
+ */
93
+ public function getAlpha() {
94
+ return $this->alpha;
95
+ }
96
+
97
+ /**
98
+ * @param float $alpha
99
+ */
100
+ public function setAlpha($alpha) {
101
+ $this->alpha = $alpha;
102
+ }
103
+
104
+
105
+ }
src/CycloneSlider/Grafika/DrawingObject/CubicBezier.php ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace CycloneSlider\Grafika\DrawingObject;
3
+
4
+ use CycloneSlider\Grafika\Color;
5
+
6
+ /**
7
+ * Base class
8
+ * @package Grafika
9
+ */
10
+ abstract class CubicBezier
11
+ {
12
+
13
+ /**
14
+ * Starting point. Array of X Y values.
15
+ * @var array
16
+ */
17
+ protected $point1;
18
+
19
+ /**
20
+ * Control point 1. Array of X Y values.
21
+ * @var array
22
+ */
23
+ protected $control1;
24
+
25
+ /**
26
+ * Control point 2. Array of X Y values.
27
+ * @var array
28
+ */
29
+ protected $control2;
30
+
31
+ /**
32
+ * End point. Array of X Y values.
33
+ * @var array
34
+ */
35
+ protected $point2;
36
+
37
+ /**
38
+ * Color of curve.
39
+ *
40
+ * @var Color
41
+ */
42
+ protected $color;
43
+
44
+ /**
45
+ * CubicBezier constructor.
46
+ * @param $point1
47
+ * @param $control1
48
+ * @param $control2
49
+ * @param $point2
50
+ * @param Color $color
51
+ */
52
+ public function __construct($point1, $control1, $control2, $point2, Color $color)
53
+ {
54
+
55
+ $this->point1 = $point1;
56
+ $this->control1 = $control1;
57
+ $this->control2 = $control2;
58
+ $this->point2 = $point2;
59
+ $this->color = $color;
60
+
61
+ }
62
+
63
+ /**
64
+ * @return array
65
+ */
66
+ public function getPoint1()
67
+ {
68
+ return $this->point1;
69
+ }
70
+
71
+ /**
72
+ * @return array
73
+ */
74
+ public function getControl1()
75
+ {
76
+ return $this->control1;
77
+ }
78
+
79
+ /**
80
+ * @return array
81
+ */
82
+ public function getControl2()
83
+ {
84
+ return $this->control2;
85
+ }
86
+
87
+ /**
88
+ * @return array
89
+ */
90
+ public function getPoint2()
91
+ {
92
+ return $this->point2;
93
+ }
94
+
95
+ /**
96
+ * @return Color
97
+ */
98
+ public function getColor()
99
+ {
100
+ return $this->color;
101
+ }
102
+
103
+ }
src/CycloneSlider/Grafika/DrawingObject/Ellipse.php ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace CycloneSlider\Grafika\DrawingObject;
3
+
4
+ use CycloneSlider\Grafika\Color;
5
+
6
+ /**
7
+ * Base class
8
+ * @package Grafika
9
+ */
10
+ abstract class Ellipse
11
+ {
12
+
13
+ /**
14
+ * Image width in pixels
15
+ * @var int
16
+ */
17
+ protected $width;
18
+
19
+ /**
20
+ * Image height in pixels
21
+ * @var int
22
+ */
23
+ protected $height;
24
+
25
+ /**
26
+ * X,Y pos.
27
+ * @var array
28
+ */
29
+ protected $pos;
30
+
31
+ /**
32
+ * @var int
33
+ */
34
+ protected $borderSize;
35
+
36
+ /**
37
+ * @var Color
38
+ */
39
+ protected $fillColor;
40
+
41
+ /**
42
+ * @var Color
43
+ */
44
+ protected $borderColor;
45
+
46
+
47
+ /**
48
+ * Ellipse constructor.
49
+ *
50
+ * @param int $width Width of ellipse in pixels.
51
+ * @param int $height Height of ellipse in pixels.
52
+ * @param array $pos Array containing int X and int Y position from top left of canvass.
53
+ * @param int $borderSize Border thickness in pixels.
54
+ * @param Color|null $borderColor Border color.
55
+ * @param Color|null $fillColor Border color.
56
+ */
57
+ public function __construct($width, $height, $pos, $borderSize, $borderColor, $fillColor) {
58
+ $this->width = $width;
59
+ $this->height = $height;
60
+ $this->pos = $pos;
61
+ $this->borderSize = $borderSize;
62
+ $this->borderColor = $borderColor;
63
+ $this->fillColor = $fillColor;
64
+ }
65
+
66
+ /**
67
+ * @return int
68
+ */
69
+ public function getWidth()
70
+ {
71
+ return $this->width;
72
+ }
73
+
74
+ /**
75
+ * @return int
76
+ */
77
+ public function getHeight()
78
+ {
79
+ return $this->height;
80
+ }
81
+
82
+ /**
83
+ * @return array
84
+ */
85
+ public function getPos()
86
+ {
87
+ return $this->pos;
88
+ }
89
+
90
+ /**
91
+ * @return int
92
+ */
93
+ public function getBorderSize()
94
+ {
95
+ return $this->borderSize;
96
+ }
97
+
98
+ /**
99
+ * @return Color
100
+ */
101
+ public function getFillColor()
102
+ {
103
+ return $this->fillColor;
104
+ }
105
+
106
+ /**
107
+ * @return Color
108
+ */
109
+ public function getBorderColor()
110
+ {
111
+ return $this->borderColor;
112
+ }
113
+
114
+
115
+ }
src/CycloneSlider/Grafika/DrawingObject/Line.php ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace CycloneSlider\Grafika\DrawingObject;
3
+
4
+ use CycloneSlider\Grafika\Color;
5
+
6
+ /**
7
+ * Base class
8
+ * @package Grafika
9
+ */
10
+ abstract class Line
11
+ {
12
+
13
+ /**
14
+ * X,Y pos 1.
15
+ * @var array
16
+ */
17
+ protected $point1;
18
+
19
+ /**
20
+ * X,Y pos 2.
21
+ * @var array
22
+ */
23
+ protected $point2;
24
+
25
+ /**
26
+ * @var int Thickness of line.
27
+ */
28
+ protected $thickness;
29
+
30
+ /**
31
+ * @var Color
32
+ */
33
+ protected $color;
34
+
35
+ /**
36
+ * Creates a line.
37
+ *
38
+ * @param array $point1 Array containing int X and int Y position of the starting point.
39
+ * @param array $point2 Array containing int X and int Y position of the starting point.
40
+ * @param int $thickness Thickness in pixel. Note: This is currently ignored in GD editor and falls back to 1.
41
+ * @param Color $color Color of the line.
42
+ */
43
+ public function __construct(array $point1, array $point2, $thickness = 1, Color $color)
44
+ {
45
+ $this->point1 = $point1;
46
+ $this->point2 = $point2;
47
+ $this->thickness = $thickness;
48
+ $this->color = $color;
49
+ }
50
+
51
+ /**
52
+ * @return array
53
+ */
54
+ public function getPoint1()
55
+ {
56
+ return $this->point1;
57
+ }
58
+
59
+ /**
60
+ * @return array
61
+ */
62
+ public function getPoint2()
63
+ {
64
+ return $this->point2;
65
+ }
66
+
67
+ /**
68
+ * @return int
69
+ */
70
+ public function getThickness()
71
+ {
72
+ return $this->thickness;
73
+ }
74
+
75
+ /**
76
+ * @return Color
77
+ */
78
+ public function getColor()
79
+ {
80
+ return $this->color;
81
+ }
82
+
83
+ }
src/CycloneSlider/Grafika/DrawingObject/Polygon.php ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace CycloneSlider\Grafika\DrawingObject;
3
+
4
+ use CycloneSlider\Grafika\Color;
5
+
6
+ /**
7
+ * Base class
8
+ * @package Grafika
9
+ */
10
+ abstract class Polygon
11
+ {
12
+ /**
13
+ * Image width in pixels
14
+ * @var int
15
+ */
16
+ protected $width;
17
+
18
+ /**
19
+ * Image height in pixels
20
+ * @var int
21
+ */
22
+ protected $height;
23
+
24
+ /**
25
+ * Array of all X and Y positions. Must have at least three positions (x,y).
26
+ * @var array
27
+ */
28
+ protected $points;
29
+
30
+ /**
31
+ * @var int
32
+ */
33
+ protected $borderSize;
34
+
35
+ /**
36
+ * @var Color
37
+ */
38
+ protected $fillColor;
39
+
40
+ /**
41
+ * @var Color
42
+ */
43
+ protected $borderColor;
44
+
45
+ /**
46
+ * Creates a polygon.
47
+ * @param array $points Array of all X and Y positions. Must have at least three positions.
48
+ * @param int $borderSize Size of the border in pixels. Defaults to 0 or no border.
49
+ * @param Color $borderColor Border color. Defaults to black.
50
+ * @param Color $fillColor Fill color. Defaults to none (null).
51
+ */
52
+ public function __construct($points = array(array(0,0), array(0,0), array(0,0)), $borderSize, $borderColor, $fillColor) {
53
+ $this->points = $points;
54
+ $this->borderSize = $borderSize;
55
+ $this->borderColor = $borderColor;
56
+ $this->fillColor = $fillColor;
57
+ }
58
+
59
+ /**
60
+ * @return int
61
+ */
62
+ public function getWidth()
63
+ {
64
+ return $this->width;
65
+ }
66
+
67
+ /**
68
+ * @return int
69
+ */
70
+ public function getHeight()
71
+ {
72
+ return $this->height;
73
+ }
74
+
75
+ /**
76
+ * @return array
77
+ */
78
+ public function getPoints()
79
+ {
80
+ return $this->points;
81
+ }
82
+
83
+ /**
84
+ * @return int
85
+ */
86
+ public function getBorderSize()
87
+ {
88
+ return $this->borderSize;
89
+ }
90
+
91
+ /**
92
+ * @return Color
93
+ */
94
+ public function getFillColor()
95
+ {
96
+ return $this->fillColor;
97
+ }
98
+
99
+ /**
100
+ * @return Color
101
+ */
102
+ public function getBorderColor()
103
+ {
104
+ return $this->borderColor;
105
+ }
106
+
107
+
108
+ }
src/CycloneSlider/Grafika/DrawingObject/QuadraticBezier.php ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace CycloneSlider\Grafika\DrawingObject;
3
+
4
+ use CycloneSlider\Grafika\Color;
5
+
6
+ /**
7
+ * Base class
8
+ * @package Grafika
9
+ */
10
+ abstract class QuadraticBezier
11
+ {
12
+
13
+ /**
14
+ * Starting point.
15
+ * @var array
16
+ */
17
+ protected $point1;
18
+
19
+ /**
20
+ * Control point.
21
+ * @var array
22
+ */
23
+ protected $control;
24
+
25
+ /**
26
+ * End point.
27
+ * @var array
28
+ */
29
+ protected $point2;
30
+
31
+ /**
32
+ * Color of curve.
33
+ *
34
+ * @var Color
35
+ */
36
+ protected $color;
37
+
38
+ public function __construct($point1, $control, $point2, $color = null)
39
+ {
40
+
41
+ $this->point1 = $point1;
42
+ $this->control = $control;
43
+ $this->point2 = $point2;
44
+ $this->color = $color;
45
+ if (null === $color) {
46
+ $this->color = new Color('#000000');
47
+ } else {
48
+ if (is_string($color)) {
49
+ $this->color = new Color($color);
50
+ }
51
+ }
52
+ }
53
+
54
+ /**
55
+ * @return array
56
+ */
57
+ public function getPoint1()
58
+ {
59
+ return $this->point1;
60
+ }
61
+
62
+ /**
63
+ * @return array
64
+ */
65
+ public function getControl()
66
+ {
67
+ return $this->control;
68
+ }
69
+
70
+ /**
71
+ * @return array
72
+ */
73
+ public function getPoint2()
74
+ {
75
+ return $this->point2;
76
+ }
77
+
78
+ /**
79
+ * @return Color
80
+ */
81
+ public function getColor()
82
+ {
83
+ return $this->color;
84
+ }
85
+
86
+ }
src/CycloneSlider/Grafika/DrawingObject/Rectangle.php ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace CycloneSlider\Grafika\DrawingObject;
3
+
4
+ use CycloneSlider\Grafika\Color;
5
+
6
+ /**
7
+ * Base class
8
+ * @package Grafika
9
+ */
10
+ abstract class Rectangle
11
+ {
12
+ /**
13
+ * Image width in pixels
14
+ * @var int
15
+ */
16
+ protected $width;
17
+
18
+ /**
19
+ * Image height in pixels
20
+ * @var int
21
+ */
22
+ protected $height;
23
+
24
+ /**
25
+ * X and Y position in an array.
26
+ * @var array
27
+ */
28
+ protected $pos;
29
+
30
+ /**
31
+ * @var int
32
+ */
33
+ protected $borderSize;
34
+
35
+ /**
36
+ * @var Color
37
+ */
38
+ protected $fillColor;
39
+
40
+ /**
41
+ * @var Color
42
+ */
43
+ protected $borderColor;
44
+
45
+ /**
46
+ * Creates a rectangle.
47
+ * @param int $width Width of rectangle in pixels.
48
+ * @param int $height Height in pixels.
49
+ * @param array $pos Array containing int X and int Y position. X is the distance in pixels from the left of the canvass to the left of the shape. Y is the distance from the top of the canvass to the top of the shape.
50
+ * @param int $borderSize Size of the border in pixels.
51
+ * @param Color|null $borderColor Border color.
52
+ * @param Color|null $fillColor Fill color.
53
+ */
54
+ public function __construct($width, $height, $pos, $borderSize, $borderColor, $fillColor) {
55
+ $this->width = $width;
56
+ $this->height = $height;
57
+ $this->pos = $pos;
58
+ $this->borderSize = $borderSize;
59
+ $this->borderColor = $borderColor;
60
+ $this->fillColor = $fillColor;
61
+ }
62
+
63
+ /**
64
+ * @return int
65
+ */
66
+ public function getWidth()
67
+ {
68
+ return $this->width;
69
+ }
70
+
71
+ /**
72
+ * @return int
73
+ */
74
+ public function getHeight()
75
+ {
76
+ return $this->height;
77
+ }
78
+
79
+ /**
80
+ * @return array
81
+ */
82
+ public function getPos()
83
+ {
84
+ return $this->pos;
85
+ }
86
+
87
+ /**
88
+ * @return int
89
+ */
90
+ public function getBorderSize()
91
+ {
92
+ return $this->borderSize;
93
+ }
94
+
95
+ /**
96
+ * @return Color
97
+ */
98
+ public function getFillColor()
99
+ {
100
+ return $this->fillColor;
101
+ }
102
+
103
+ /**
104
+ * @return Color
105
+ */
106
+ public function getBorderColor()
107
+ {
108
+ return $this->borderColor;
109
+ }
110
+
111
+
112
+ }
src/CycloneSlider/Grafika/DrawingObjectInterface.php ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace CycloneSlider\Grafika;
3
+
4
+ /**
5
+ * Interface DrawingObjectInterface
6
+ * @package Grafika
7
+ */
8
+ interface DrawingObjectInterface {
9
+
10
+ /**
11
+ * @param ImageInterface $image
12
+ *
13
+ * @return ImageInterface
14
+ */
15
+ public function draw( $image );
16
+
17
+ }
src/CycloneSlider/Grafika/EditorInterface.php ADDED
@@ -0,0 +1,348 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace CycloneSlider\Grafika;
3
+
4
+ /**
5
+ * Interface EditorInterface
6
+ * @package Grafika
7
+ */
8
+ interface EditorInterface {
9
+
10
+ /**
11
+ * Apply an effect to the image. See Effects section for a list of effects to apply.
12
+ *
13
+ * @param EffectInterface $effect
14
+ *
15
+ * @return $this
16
+ */
17
+ public function apply( $effect );
18
+
19
+ /**
20
+ * Creates a cubic bezier. Cubic bezier has 2 control points.
21
+ * @param array $point1 Array of X and Y value for start point.
22
+ * @param array $control1 Array of X and Y value for control point 1.
23
+ * @param array $control2 Array of X and Y value for control point 2.
24
+ * @param array $point2 Array of X and Y value for end point.
25
+ * @param Color|string $color Color of the curve. Accepts hex string or a Color object. Defaults to black.
26
+ * @return EditorInterface An instance of image editor.
27
+ */
28
+ public function bezierCubic($point1, $control1, $control2, $point2, $color = '#000000');
29
+
30
+ /**
31
+ * Creates a quadratic bezier. Quadratic bezier has 1 control point.
32
+ * @param array $point1 Array of X and Y value for start point.
33
+ * @param array $control Array of X and Y value for control point.
34
+ * @param array $point2 Array of X and Y value for end point.
35
+ * @param Color|string $color Color of the curve. Accepts hex string or a Color object. Defaults to black.
36
+ * @return EditorInterface An instance of image editor.
37
+ */
38
+ public function bezierQuad($point1, $control, $point2, $color = '#000000');
39
+
40
+ /**
41
+ * Create a blank image given width and height.
42
+ *
43
+ * @param int $width Width of image in pixels.
44
+ * @param int $height Height of image in pixels.
45
+ *
46
+ * @return EditorInterface An instance of image editor.
47
+ */
48
+ public function blank( $width, $height );
49
+
50
+ /**
51
+ * Compare two images and returns a hamming distance. A value of 0 indicates a likely similar picture. A value between 1 and 10 is potentially a variation. A value greater than 10 is likely a different image.
52
+ *
53
+ * @param string|ImageInterface $image1 Can be an instance of Image or string containing the file system path to image.
54
+ * @param string|ImageInterface $image2 Can be an instance of Image or string containing the file system path to image.
55
+ *
56
+ * @return int Hamming distance. Note: This breaks the chain if you are doing fluent api calls as it does not return an Editor.
57
+ * @throws \Exception
58
+ */
59
+ public function compare( $image1, $image2 );
60
+
61
+ /**
62
+ * Crop the image to the given dimension and position.
63
+ *
64
+ * @param int $cropWidth Crop width in pixels.
65
+ * @param int $cropHeight Crop Height in pixels.
66
+ * @param int|string $cropX The number of pixels from the left of the image. This parameter can be a number or any of the words "left", "center", "right".
67
+ * @param int|string $cropY The number of pixels from the top of the image. This parameter can be a number or any of the words "top", "center", "bottom".
68
+ *
69
+ * @return EditorInterface An instance of image editor.
70
+ */
71
+ public function crop( $cropWidth, $cropHeight, $cropX='center', $cropY='center');
72
+
73
+ /**
74
+ * Dither image using Floyd-Steinberg algorithm. Dithering will reduce the color to black and white and add noise.
75
+ * @return EditorInterface An instance of image editor.
76
+ */
77
+ public function dither();
78
+
79
+ /**
80
+ * Draw a DrawingObject on the image. See Drawing Objects section.
81
+ *
82
+ * @param DrawingObjectInterface $drawingObject
83
+ *
84
+ * @return EditorInterface An instance of image editor.
85
+ */
86
+ public function draw( $drawingObject );
87
+
88
+ /**
89
+ * Creates an ellipse.
90
+ *
91
+ * @param int $width Width of ellipse in pixels.
92
+ * @param int $height Height of ellipse in pixels.
93
+ * @param array $pos Array containing int X and int Y position of the ellipse from top left of the canvass.
94
+ * @param int $borderSize Size of the border in pixels. Defaults to 1 pixel. Set to 0 for no border.
95
+ * @param Color|string|null $borderColor Border color. Defaults to black. Set to null for no color.
96
+ * @param Color|string|null $fillColor Fill color. Defaults to white. Set to null for no color.
97
+ *
98
+ * @return EditorInterface An instance of image editor.
99
+ */
100
+ public function ellipse($width, $height, array $pos, $borderSize = 1, $borderColor = '#000000', $fillColor = '#FFFFFF');
101
+
102
+ /**
103
+ * Compare if two images are equal. It will compare if the two images are of the same width and height. If the dimensions differ, it will return false. If the dimensions are equal, it will loop through each pixels. If one of the pixel don't match, it will return false. The pixels are compared using their RGB (Red, Green, Blue) values.
104
+ *
105
+ * @param string|ImageInterface $image1 Can be an instance of Image or string containing the file system path to image.
106
+ * @param string|ImageInterface $image2 Can be an instance of Image or string containing the file system path to image.
107
+ *
108
+ * @return bool True if equals false if not. Note: This breaks the chain if you are doing fluent api calls as it does not return an Editor.
109
+ * @throws \Exception
110
+ */
111
+ public function equal( $image1, $image2 );
112
+
113
+ /**
114
+ * Fill entire image with color.
115
+ *
116
+ * @param Color $color An instance of Grafika\Color class.
117
+ * @param int $x X-coordinate of start point.
118
+ * @param int $y Y-coordinate of start point.
119
+ *
120
+ * @return EditorInterface An instance of image editor.
121
+ */
122
+ public function fill( $color, $x = 0, $y = 0 );
123
+
124
+ /**
125
+ * Free the current image clearing resources associated with it.
126
+ */
127
+ public function free();
128
+
129
+ /**
130
+ * Converts image to grayscale.
131
+ *
132
+ * @return EditorInterface An instance of image editor.
133
+ */
134
+ public function grayscale();
135
+
136
+ /**
137
+ * Alias for grayscale.
138
+ *
139
+ * @return EditorInterface An instance of image editor.
140
+ */
141
+ public function greyscale();
142
+
143
+ /**
144
+ * Get image instance.
145
+ *
146
+ * @return ImageInterface An instance of image.
147
+ */
148
+ public function getImage();
149
+
150
+ /**
151
+ * Checks the PHP install if the editor is available.
152
+ *
153
+ * @return bool True if available false if not.
154
+ */
155
+ public function isAvailable();
156
+
157
+ /**
158
+ * Creates a line.
159
+ *
160
+ * @param array $point1 Array containing int X and int Y position of the starting point.
161
+ * @param array $point2 Array containing int X and int Y position of the starting point.
162
+ * @param int $thickness Thickness in pixel. Note: This is currently ignored in GD editor and falls back to 1.
163
+ * @param Color|string $color Color of the line. Defaults to black.
164
+ *
165
+ * @return EditorInterface An instance of image editor.
166
+ */
167
+ public function line(array $point1, array $point2, $thickness = 1, $color = '#000000');
168
+
169
+ /**
170
+ * Change the image opacity.
171
+ *
172
+ * @param float $opacity The opacity level where 1.0 is fully opaque and 0.0 is fully transparent.
173
+ *
174
+ * @return EditorInterface An instance of image editor.
175
+ */
176
+ public function opacity( $opacity );
177
+
178
+ /**
179
+ * Opens an image file for manipulation specified by $target.
180
+ *
181
+ * @param mixed $target Can be an instance of Image or a string containing file system path to the image.
182
+ *
183
+ * @return EditorInterface An instance of image editor.
184
+ */
185
+ public function open( $target );
186
+
187
+ /**
188
+ * Open an image by passing an instance of Image.
189
+ *
190
+ * @param ImageInterface $image
191
+ *
192
+ * @return $this
193
+ */
194
+ public function openImage( $image );
195
+
196
+ /**
197
+ * Open an image by passing a file system path.
198
+ *
199
+ * @param string $file A full path to the image in the file system.
200
+ *
201
+ * @return $this
202
+ * @throws \Exception
203
+ */
204
+ public function openFile( $file );
205
+
206
+ /**
207
+ * Overlay an image on top of the current image.
208
+ *
209
+ * @param ImageInterface|string $overlay Can be a string containing a file path of the image to overlay or an Image object.
210
+ * @param string|int $xPos Horizontal position of image. Can be 'left','center','right' or integer number. Defaults to 'center'.
211
+ * @param string|int $yPos Vertical position of image. Can be 'top', 'center','bottom' or integer number. Defaults to 'center'.
212
+ * @param null $width Width of overlay in pixels.
213
+ * @param null $height Height of overlay in pixels.
214
+ *
215
+ * @return EditorInterface An instance of image editor.
216
+ */
217
+ public function overlay( $overlay, $xPos = 'center', $yPos = 'center', $width = null, $height = null );
218
+
219
+ /**
220
+ * Creates a polygon.
221
+ *
222
+ * @param array $points Array of all X and Y positions. Must have at least three positions.
223
+ * @param int $borderSize Size of the border in pixels. Defaults to 1 pixel. Set to 0 for no border.
224
+ * @param Color|string|null $borderColor Border color. Defaults to black. Set to null for no color.
225
+ * @param Color|string|null $fillColor Fill color. Defaults to white. Set to null for no color.
226
+ *
227
+ * @return EditorInterface An instance of image editor.
228
+ */
229
+ public function polygon($points, $borderSize = 1, $borderColor = '#000000', $fillColor = '#FFFFFF');
230
+
231
+ /**
232
+ * Creates a rectangle.
233
+ * @param int $width Width of rectangle in pixels.
234
+ * @param int $height Height in pixels.
235
+ * @param array $pos Array of X and Y position. X is the distance in pixels from the left of the canvass to the left of the rectangle. Y is the distance from the top of the canvass to the top of the rectangle. Defaults to array(0,0).
236
+ * @param int $borderSize Size of the border in pixels. Defaults to 1 pixel. Set to 0 for no border.
237
+ * @param Color|string|null $borderColor Border color. Defaults to black. Set to null for no color.
238
+ * @param Color|string|null $fillColor Fill color. Defaults to white. Set to null for no color.
239
+ *
240
+ * @return EditorInterface An instance of image editor.
241
+ */
242
+ public function rectangle($width, $height, $pos = array(0, 0), $borderSize = 1, $borderColor = '#000000', $fillColor = '#FFFFFF');
243
+
244
+ /**
245
+ * Wrapper function for the resizeXXX family of functions. Resize an image to a given width, height and mode.
246
+ *
247
+ * @param int $newWidth Width in pixels.
248
+ * @param int $newHeight Height in pixels.
249
+ * @param string $mode Resize mode. Possible values: "exact", "exactHeight", "exactWidth", "fill", "fit".
250
+ *
251
+ * @return EditorInterface An instance of image editor.
252
+ */
253
+ public function resize( $newWidth, $newHeight, $mode='fit' );
254
+
255
+ /**
256
+ * Resize image to exact dimensions ignoring aspect ratio. Useful if you want to force exact width and height.
257
+ *
258
+ * @param int $newWidth Width in pixels.
259
+ * @param int $newHeight Height in pixels.
260
+ *
261
+ * @return EditorInterface An instance of image editor.
262
+ */
263
+ public function resizeExact( $newWidth, $newHeight );
264
+
265
+ /**
266
+ * Resize image to exact height. Width is auto calculated. Useful for creating row of images with the same height.
267
+ *
268
+ * @param int $newHeight Height in pixels.
269
+ *
270
+ * @return EditorInterface An instance of image editor.
271
+ */
272
+ public function resizeExactHeight( $newHeight );
273
+
274
+ /**
275
+ * Resize image to exact width. Height is auto calculated. Useful for creating column of images with the same width.
276
+ *
277
+ * @param int $newWidth Width in pixels.
278
+ *
279
+ * @return EditorInterface An instance of image editor.
280
+ */
281
+ public function resizeExactWidth( $newWidth );
282
+
283
+ /**
284
+ * Resize image to fill all the space in the given dimension. Excess parts are cropped.
285
+ *
286
+ * @param int $newWidth Width in pixels.
287
+ * @param int $newHeight Height in pixels.
288
+ *
289
+ * @return EditorInterface An instance of image editor.
290
+ */
291
+ public function resizeFill( $newWidth, $newHeight );
292
+
293
+ /**
294
+ * Resize an image to fit within the given width and height. The re-sized image will not exceed the given dimension. Useful if you want to preserve the aspect ratio.
295
+ *
296
+ * @param int $newWidth Width in pixels.
297
+ * @param int $newHeight Width in pixels.
298
+ *
299
+ * @return EditorInterface An instance of image editor.
300
+ */
301
+ public function resizeFit( $newWidth, $newHeight );
302
+
303
+ /**
304
+ * Rotate an image counter-clockwise.
305
+ *
306
+ * @param int $angle The angle in degrees.
307
+ * @param Color|null $color The Color object containing the background color.
308
+ *
309
+ * @return EditorInterface An instance of image editor.
310
+ */
311
+ public function rotate( $angle, $color = null );
312
+
313
+ /**
314
+ * Save the image to an image format.
315
+ *
316
+ * @param string $file File path where to save the image.
317
+ * @param null|string $type Type of image. Can be null, "GIF", "PNG", or "JPEG". If null, an appropriate file type will be used.
318
+ * @param null|string $quality Quality of image. Applies to JPEG only. Accepts number 0 - 100 where 0 is lowest and 100 is the highest quality. Or null for default.
319
+ * @param bool $interlace Set to true for progressive JPEG. Applies to JPEG only.
320
+ *
321
+ * @return EditorInterface An instance of image editor.
322
+ */
323
+ public function save( $file, $type = null, $quality = null, $interlace = false );
324
+
325
+ /**
326
+ * Set image instance.
327
+ *
328
+ * @param ImageInterface $image An instance of a class implementing Grafika/ImageInterface.
329
+ */
330
+ public function setImage( $image );
331
+
332
+ /**
333
+ * Write text to image.
334
+ *
335
+ * @param string $text The text to be written.
336
+ * @param int $size The font size. Defaults to 12.
337
+ * @param int $x The distance from the left edge of the image to the left of the text. Defaults to 0.
338
+ * @param int $y The distance from the top edge of the image to the baseline of the text. Defaults to 12 (equal to font size) so that the text is placed within the image.
339
+ * @param Color $color The Color object. Default text color is black.
340
+ * @param string $font Full path to font file. If blank, will default to Liberation Sans font.
341
+ * @param int $angle Angle of text from 0 - 359. Defaults to 0.
342
+ *
343
+ * @return EditorInterface An instance of image editor.
344
+ * @throws \Exception
345
+ */
346
+ public function text( $text, $size = 12, $x = 0, $y = 12, $color = null, $font = '', $angle = 0 );
347
+
348
+ }
src/CycloneSlider/Grafika/EffectInterface.php ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace CycloneSlider\Grafika;
3
+
4
+ /**
5
+ * Interface EffectInterface
6
+ * @package Grafika
7
+ */
8
+ interface EffectInterface {
9
+
10
+ /**
11
+ * @param ImageInterface $image
12
+ *
13
+ * @return ImageInterface
14
+ */
15
+ public function apply( $image );
16
+
17
+ }
src/CycloneSlider/Grafika/Gd/DrawingObject/CubicBezier.php ADDED
@@ -0,0 +1,412 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace CycloneSlider\Grafika\Gd\DrawingObject;
3
+
4
+ use CycloneSlider\Grafika\DrawingObject\CubicBezier as Base;
5
+ use CycloneSlider\Grafika\DrawingObjectInterface;
6
+ use CycloneSlider\Grafika\Gd\Image;
7
+ use CycloneSlider\Grafika\ImageInterface;
8
+
9
+ /**
10
+ * Class CubicBezier
11
+ * @package Grafika
12
+ */
13
+ class CubicBezier extends Base implements DrawingObjectInterface
14
+ {
15
+
16
+ /**
17
+ * @param ImageInterface $image
18
+ * @return Image
19
+ */
20
+ public function draw($image)
21
+ {
22
+ // Localize vars
23
+ $width = $image->getWidth();
24
+ $height = $image->getHeight();
25
+ $gd = $image->getCore();
26
+
27
+ list($x0, $y0) = $this->point1;
28
+ list($x1, $y1) = $this->control1;
29
+ list($x2, $y2) = $this->control2;
30
+ list($x3, $y3) = $this->point2;
31
+
32
+ $this->plot($gd, $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3);
33
+
34
+ $type = $image->getType();
35
+ $file = $image->getImageFile();
36
+ return new Image($gd, $file, $width, $height, $type); // Create new image with updated core
37
+ }
38
+
39
+ protected function plot($gd, $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
40
+ { /* plot any cubic Bezier curve */
41
+ $n = 0;
42
+ $i = 0;
43
+ $xc = $x0 + $x1 - $x2 - $x3;
44
+ $xa = $xc - 4 * ($x1 - $x2);
45
+ $xb = $x0 - $x1 - $x2 + $x3;
46
+ $xd = $xb + 4 * ($x1 + $x2);
47
+ $yc = $y0 + $y1 - $y2 - $y3;
48
+ $ya = $yc - 4 * ($y1 - $y2);
49
+ $yb = $y0 - $y1 - $y2 + $y3;
50
+ $yd = $yb + 4 * ($y1 + $y2);
51
+ $fx0 = $x0;
52
+ $fx1 = 0;
53
+ $fx2 = 0;
54
+ $fx3 = 0;
55
+ $fy0 = $y0;
56
+ $fy1 = 0;
57
+ $fy2 = 0;
58
+ $fy3 = 0;
59
+ $t1 = $xb * $xb - $xa * $xc;
60
+ $t2 = 0;
61
+ $t = array();
62
+ /* sub-divide curve at gradient sign changes */
63
+ if ($xa == 0) { /* horizontal */
64
+ if (abs($xc) < 2 * abs($xb)) {
65
+ $t[$n++] = $xc / (2.0 * $xb);
66
+ } /* one change */
67
+ } else {
68
+ if ($t1 > 0.0) { /* two changes */
69
+ $t2 = sqrt($t1);
70
+ $t1 = ($xb - $t2) / $xa;
71
+ if (abs($t1) < 1.0) {
72
+ $t[$n++] = $t1;
73
+ }
74
+ $t1 = ($xb + $t2) / $xa;
75
+ if (abs($t1) < 1.0) {
76
+ $t[$n++] = $t1;
77
+ }
78
+ }
79
+ }
80
+ $t1 = $yb * $yb - $ya * $yc;
81
+ if ($ya == 0) { /* vertical */
82
+ if (abs($yc) < 2 * abs($yb)) {
83
+ $t[$n++] = $yc / (2.0 * $yb);
84
+ } /* one change */
85
+ } else {
86
+ if ($t1 > 0.0) { /* two changes */
87
+ $t2 = sqrt($t1);
88
+ $t1 = ($yb - $t2) / $ya;
89
+ if (abs($t1) < 1.0) {
90
+ $t[$n++] = $t1;
91
+ }
92
+ $t1 = ($yb + $t2) / $ya;
93
+ if (abs($t1) < 1.0) {
94
+ $t[$n++] = $t1;
95
+ }
96
+ }
97
+ }
98
+ for ($i = 1; $i < $n; $i++) /* bubble sort of 4 points */ {
99
+ if (($t1 = $t[$i - 1]) > $t[$i]) {
100
+ $t[$i - 1] = $t[$i];
101
+ $t[$i] = $t1;
102
+ $i = 0;
103
+ }
104
+ }
105
+ $t1 = -1.0;
106
+ $t[$n] = 1.0; /* begin / end point */
107
+ for ($i = 0; $i <= $n; $i++) { /* plot each segment separately */
108
+ $t2 = $t[$i]; /* sub-divide at $t[$i-1], $t[$i] */
109
+ $fx1 = ($t1 * ($t1 * $xb - 2 * $xc) - $t2 * ($t1 * ($t1 * $xa - 2 * $xb) + $xc) + $xd) / 8 - $fx0;
110
+ $fy1 = ($t1 * ($t1 * $yb - 2 * $yc) - $t2 * ($t1 * ($t1 * $ya - 2 * $yb) + $yc) + $yd) / 8 - $fy0;
111
+ $fx2 = ($t2 * ($t2 * $xb - 2 * $xc) - $t1 * ($t2 * ($t2 * $xa - 2 * $xb) + $xc) + $xd) / 8 - $fx0;
112
+ $fy2 = ($t2 * ($t2 * $yb - 2 * $yc) - $t1 * ($t2 * ($t2 * $ya - 2 * $yb) + $yc) + $yd) / 8 - $fy0;
113
+ $fx0 -= $fx3 = ($t2 * ($t2 * (3 * $xb - $t2 * $xa) - 3 * $xc) + $xd) / 8;
114
+ $fy0 -= $fy3 = ($t2 * ($t2 * (3 * $yb - $t2 * $ya) - 3 * $yc) + $yd) / 8;
115
+ $x3 = floor($fx3 + 0.5);
116
+ $y3 = floor($fy3 + 0.5); /* scale bounds to int */
117
+ if ($fx0 != 0.0) {
118
+ $fx1 *= $fx0 = ($x0 - $x3) / $fx0;
119
+ $fx2 *= $fx0;
120
+ }
121
+ if ($fy0 != 0.0) {
122
+ $fy1 *= $fy0 = ($y0 - $y3) / $fy0;
123
+ $fy2 *= $fy0;
124
+ }
125
+ if ($x0 != $x3 || $y0 != $y3) /* segment $t1 - $t2 */ {
126
+ $this->plotCubicSegment($gd, $x0, $y0, $x0 + $fx1, $y0 + $fy1, $x0 + $fx2, $y0 + $fy2, $x3, $y3);
127
+ }
128
+ $x0 = $x3;
129
+ $y0 = $y3;
130
+ $fx0 = $fx3;
131
+ $fy0 = $fy3;
132
+ $t1 = $t2;
133
+ }
134
+ }
135
+
136
+ protected function plotCubicSegment($gd, $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
137
+ { /* plot limited anti-aliased cubic Bezier segment */
138
+ $f = 0;
139
+ $fx = 0;
140
+ $fy = 0;
141
+ $leg = 1;
142
+ $sx = $x0 < $x3 ? 1 : -1;
143
+ $sy = $y0 < $y3 ? 1 : -1; /* step direction */
144
+
145
+ $xc = -abs($x0 + $x1 - $x2 - $x3);
146
+ $xa = $xc - 4 * $sx * ($x1 - $x2);
147
+ $xb = $sx * ($x0 - $x1 - $x2 + $x3);
148
+ $yc = -abs($y0 + $y1 - $y2 - $y3);
149
+ $ya = $yc - 4 * $sy * ($y1 - $y2);
150
+ $yb = $sy * ($y0 - $y1 - $y2 + $y3);
151
+
152
+ $ab = 0;
153
+ $ac = 0;
154
+ $bc = 0;
155
+ $ba = 0;
156
+ $xx = 0;
157
+ $xy = 0;
158
+ $yy = 0;
159
+ $dx = 0;
160
+ $dy = 0;
161
+ $ex = 0;
162
+ $px = 0;
163
+ $py = 0;
164
+ $ed = 0;
165
+ $ip = 0;
166
+ $EP = 0.01;
167
+ /* check for curve restrains */
168
+ /* slope P0-P1 == P2-P3 and (P0-P3 == P1-P2 or no slope change) */
169
+ assert(($x1 - $x0) * ($x2 - $x3) < $EP && (($x3 - $x0) * ($x1 - $x2) < $EP || $xb * $xb < $xa * $xc + $EP));
170
+ assert(($y1 - $y0) * ($y2 - $y3) < $EP && (($y3 - $y0) * ($y1 - $y2) < $EP || $yb * $yb < $ya * $yc + $EP));
171
+ if ($xa == 0 && $ya == 0) { /* quadratic Bezier */
172
+ $sx = floor((3 * $x1 - $x0 + 1) / 2);
173
+ $sy = floor((3 * $y1 - $y0 + 1) / 2); /* new midpoint */
174
+ $this->plotQuadSegment($gd, $x0, $y0, $sx, $sy, $x3, $y3);
175
+ return;
176
+ }
177
+ $x1 = ($x1 - $x0) * ($x1 - $x0) + ($y1 - $y0) * ($y1 - $y0) + 1; /* line lengths */
178
+ $x2 = ($x2 - $x3) * ($x2 - $x3) + ($y2 - $y3) * ($y2 - $y3) + 1;
179
+ do { /* loop over both ends */
180
+ $ab = $xa * $yb - $xb * $ya;
181
+ $ac = $xa * $yc - $xc * $ya;
182
+ $bc = $xb * $yc - $xc * $yb;
183
+ $ip = 4 * $ab * $bc - $ac * $ac; /* self intersection loop at all? */
184
+ $ex = $ab * ($ab + $ac - 3 * $bc) + $ac * $ac; /* P0 part of self-intersection loop? */
185
+ $f = $ex > 0 ? 1 : sqrt(1 + 1024 / $x1); /* calculate resolution */
186
+ $ab *= $f;
187
+ $ac *= $f;
188
+ $bc *= $f;
189
+ $ex *= $f * $f; /* increase resolution */
190
+ $xy = 9 * ($ab + $ac + $bc) / 8;
191
+ $ba = 8 * ($xa - $ya);/* init differences of 1st degree */
192
+ $dx = 27 * (8 * $ab * ($yb * $yb - $ya * $yc) + $ex * ($ya + 2 * $yb + $yc)) / 64 - $ya * $ya * ($xy - $ya);
193
+ $dy = 27 * (8 * $ab * ($xb * $xb - $xa * $xc) - $ex * ($xa + 2 * $xb + $xc)) / 64 - $xa * $xa * ($xy + $xa);
194
+ /* init differences of 2nd degree */
195
+ $xx = 3 * (3 * $ab * (3 * $yb * $yb - $ya * $ya - 2 * $ya * $yc) - $ya * (3 * $ac * ($ya + $yb) + $ya * $ba)) / 4;
196
+ $yy = 3 * (3 * $ab * (3 * $xb * $xb - $xa * $xa - 2 * $xa * $xc) - $xa * (3 * $ac * ($xa + $xb) + $xa * $ba)) / 4;
197
+ $xy = $xa * $ya * (6 * $ab + 6 * $ac - 3 * $bc + $ba);
198
+ $ac = $ya * $ya;
199
+ $ba = $xa * $xa;
200
+ $xy = 3 * ($xy + 9 * $f * ($ba * $yb * $yc - $xb * $xc * $ac) - 18 * $xb * $yb * $ab) / 8;
201
+ if ($ex < 0) { /* negate values if inside self-intersection loop */
202
+ $dx = -$dx;
203
+ $dy = -$dy;
204
+ $xx = -$xx;
205
+ $yy = -$yy;
206
+ $xy = -$xy;
207
+ $ac = -$ac;
208
+ $ba = -$ba;
209
+ } /* init differences of 3rd degree */
210
+ $ab = 6 * $ya * $ac;
211
+ $ac = -6 * $xa * $ac;
212
+ $bc = 6 * $ya * $ba;
213
+ $ba = -6 * $xa * $ba;
214
+ $dx += $xy;
215
+ $ex = $dx + $dy;
216
+ $dy += $xy; /* error of 1st step */
217
+ for ($fx = $fy = $f; $x0 != $x3 && $y0 != $y3;) {
218
+ $y1 = min($xy - $dx, $dy - $xy);
219
+ $ed = max($xy - $dx, $dy - $xy); /* approximate error distance */
220
+ $ed = $f * ($ed + 2 * $ed * $y1 * $y1 / (4 * $ed * $ed + $y1 * $y1));
221
+ $y1 = 255 * abs($ex - ($f - $fx + 1) * $dx - ($f - $fy + 1) * $dy + $f * $xy) / $ed;
222
+ if ($y1 < 256) {
223
+ $this->setPixel($gd, $x0, $y0, $y1 / 255);
224
+ } /* plot curve */
225
+ $px = abs($ex - ($f - $fx + 1) * $dx + ($fy - 1) * $dy); /* pixel intensity x move */
226
+ $py = abs($ex + ($fx - 1) * $dx - ($f - $fy + 1) * $dy); /* pixel intensity y move */
227
+ $y2 = $y0;
228
+ do { /* move sub-steps of one pixel */
229
+ if ($ip >= -$EP) /* intersection possible? -> check.. */ {
230
+ if ($dx + $xx > $xy || $dy + $yy < $xy) {
231
+ goto exits;
232
+ }
233
+ } /* two x or y steps */
234
+ $y1 = 2 * $ex + $dx; /* save value for test of y step */
235
+ if (2 * $ex + $dy > 0) { /* x sub-step */
236
+ $fx--;
237
+ $ex += $dx += $xx;
238
+ $dy += $xy += $ac;
239
+ $yy += $bc;
240
+ $xx += $ab;
241
+ } else {
242
+ if ($y1 > 0) {
243
+ goto exits;
244
+ }
245
+ } /* tiny nearly cusp */
246
+ if ($y1 <= 0) { /* y sub-step */
247
+ $fy--;
248
+ $ex += $dy += $yy;
249
+ $dx += $xy += $bc;
250
+ $xx += $ac;
251
+ $yy += $ba;
252
+ }
253
+ } while ($fx > 0 && $fy > 0); /* pixel complete? */
254
+ if (2 * $fy <= $f) { /* x+ anti-aliasing pixel */
255
+ if ($py < $ed) {
256
+ $this->setPixel($gd, $x0 + $sx, $y0, $py / $ed);
257
+ } /* plot curve */
258
+ $y0 += $sy;
259
+ $fy += $f; /* y step */
260
+ }
261
+ if (2 * $fx <= $f) { /* y+ anti-aliasing pixel */
262
+ if ($px < $ed) {
263
+ $this->setPixel($gd, $x0, $y2 + $sy, $px / $ed);
264
+ } /* plot curve */
265
+ $x0 += $sx;
266
+ $fx += $f; /* x step */
267
+ }
268
+ }
269
+ break; /* finish curve by line */
270
+ exits:
271
+ if (2 * $ex < $dy && 2 * $fy <= $f + 2) { /* round x+ approximation pixel */
272
+ if ($py < $ed) {
273
+ $this->setPixel($gd, $x0 + $sx, $y0, $py / $ed);
274
+ } /* plot curve */
275
+ $y0 += $sy;
276
+ }
277
+ if (2 * $ex > $dx && 2 * $fx <= $f + 2) { /* round y+ approximation pixel */
278
+ if ($px < $ed) {
279
+ $this->setPixel($gd, $x0, $y2 + $sy, $px / $ed);
280
+ } /* plot curve */
281
+ $x0 += $sx;
282
+ }
283
+ $xx = $x0;
284
+ $x0 = $x3;
285
+ $x3 = $xx;
286
+ $sx = -$sx;
287
+ $xb = -$xb; /* swap legs */
288
+ $yy = $y0;
289
+ $y0 = $y3;
290
+ $y3 = $yy;
291
+ $sy = -$sy;
292
+ $yb = -$yb;
293
+ $x1 = $x2;
294
+ } while ($leg--); /* try other end */
295
+ $this->plotLine($gd, $x0, $y0, $x3, $y3); /* remaining part in case of cusp or crunode */
296
+ }
297
+
298
+ protected function plotQuadSegment($gd, $x0, $y0, $x1, $y1, $x2, $y2)
299
+ { /* draw an limited anti-aliased quadratic Bezier segment */
300
+ $sx = $x2 - $x1;
301
+ $sy = $y2 - $y1;
302
+ $xx = $x0 - $x1;
303
+ $yy = $y0 - $y1;
304
+ $xy = $dx = $dy = $err = $ed = 0;
305
+ $cur = $xx * $sy - $yy * $sx; /* $curvature */
306
+ if ($sx * (int)$sx + $sy * (int)$sy > $xx * $xx + $yy * $yy) { /* begin with longer part */
307
+ $x2 = $x0;
308
+ $x0 = $sx + $x1;
309
+ $y2 = $y0;
310
+ $y0 = $sy + $y1;
311
+ $cur = -$cur; /* swap P0 P2 */
312
+ }
313
+ if ($cur != 0) { /* no straight line */
314
+ $xx += $sx;
315
+ $xx *= $sx = $x0 < $x2 ? 1 : -1; /* x step direction */
316
+ $yy += $sy;
317
+ $yy *= $sy = $y0 < $y2 ? 1 : -1; /* y step direction */
318
+ $xy = 2 * $xx * $yy;
319
+ $xx *= $xx;
320
+ $yy *= $yy; /* differences 2nd degree */
321
+ if ($cur * $sx * $sy < 0) { /* negat$ed $curvature? */
322
+ $xx = -$xx;
323
+ $yy = -$yy;
324
+ $xy = -$xy;
325
+ $cur = -$cur;
326
+ }
327
+ $dx = 4.0 * $sy * ($x1 - $x0) * $cur + $xx - $xy; /* differences 1st degree */
328
+ $dy = 4.0 * $sx * ($y0 - $y1) * $cur + $yy - $xy;
329
+ $xx += $xx;
330
+ $yy += $yy;
331
+ $err = $dx + $dy + $xy; /* $error 1st step */
332
+ do {
333
+ $cur = min($dx + $xy, -$xy - $dy);
334
+ $ed = max($dx + $xy, -$xy - $dy); /* approximate $error distance */
335
+ $ed += 2 * $ed * $cur * $cur / (4 * $ed * $ed + $cur * $cur);
336
+ $this->setPixel($gd, $x0, $y0, abs($err - $dx - $dy - $xy) / $ed); /* plot $curve */
337
+ if ($x0 == $x2 || $y0 == $y2) {
338
+ break;
339
+ } /* $curve finish$ed */
340
+ $x1 = $x0;
341
+ $cur = $dx - $err;
342
+ $y1 = 2 * $err + $dy < 0;
343
+ if (2 * $err + $dx > 0) { /* x step */
344
+ if ($err - $dy < $ed) {
345
+ $this->setPixel($gd, $x0, $y0 + $sy, abs($err - $dy) / $ed);
346
+ }
347
+ $x0 += $sx;
348
+ $dx -= $xy;
349
+ $err += $dy += $yy;
350
+ }
351
+ if ($y1) { /* y step */
352
+ if ($cur < $ed) {
353
+ $this->setPixel($gd, $x1 + $sx, $y0, abs($cur) / $ed);
354
+ }
355
+ $y0 += $sy;
356
+ $dy -= $xy;
357
+ $err += $dx += $xx;
358
+ }
359
+ } while ($dy < $dx); /* gradient negates -> close curves */
360
+ }
361
+ $this->plotLine($gd, $x0, $y0, $x2, $y2); /* plot remaining needle to end */
362
+ }
363
+
364
+ protected function plotLine($gd, $x0, $y0, $x1, $y1)
365
+ { /* draw a black (0) anti-aliased line on white (255) background */
366
+ $dx = abs($x1 - $x0);
367
+ $sx = $x0 < $x1 ? 1 : -1;
368
+ $dy = -abs($y1 - $y0);
369
+ $sy = $y0 < $y1 ? 1 : -1;
370
+ $err = $dx + $dy;
371
+ $e2 = $x2 = 0; /* $error value e_xy */
372
+ $ed = $dx - $dy == 0 ? 1 : sqrt((float)$dx * $dx + (float)$dy * $dy);
373
+ for (; ;) { /* pixel loop */
374
+ $this->setPixel($gd, $x0, $y0, abs($err - $dx - $dy) / $ed);
375
+ $e2 = $err;
376
+ $x2 = $x0;
377
+ if (2 * $e2 + $dx >= 0) { /* x step */
378
+ if ($x0 == $x1) {
379
+ break;
380
+ }
381
+ if ($e2 - $dy < $ed) {
382
+ $this->setPixel($gd, $x0, $y0 + $sy, ($e2 - $dy) / $ed);
383
+ }
384
+ $err += $dy;
385
+ $x0 += $sx;
386
+ }
387
+ if (2 * $e2 + $dy <= 0) { /* y step */
388
+ if ($y0 == $y1) {
389
+ break;
390
+ }
391
+ if ($dx - $e2 < $ed) {
392
+ $this->setPixel($gd, $x2 + $sx, $y0, ($dx - $e2) / $ed);
393
+ }
394
+ $err += $dx;
395
+ $y0 += $sy;
396
+ }
397
+ }
398
+ }
399
+
400
+ /**
401
+ * @param resource $gd
402
+ * @param int $x
403
+ * @param int $y
404
+ * @param float $ar Alpha ratio
405
+ */
406
+ protected function setPixel($gd, $x, $y, $ar)
407
+ {
408
+ list($r, $g, $b) = $this->color->getRgb();
409
+ $c = imagecolorallocatealpha($gd, $r, $g, $b, 127 * $ar);
410
+ imagesetpixel($gd, $x, $y, $c);
411
+ }
412
+ }
src/CycloneSlider/Grafika/Gd/DrawingObject/Ellipse.php ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace CycloneSlider\Grafika\Gd\DrawingObject;
3
+
4
+ use CycloneSlider\Grafika\DrawingObject\Ellipse as Base;
5
+ use CycloneSlider\Grafika\DrawingObjectInterface;
6
+ use CycloneSlider\Grafika\Gd\Editor;
7
+ use CycloneSlider\Grafika\ImageInterface;
8
+
9
+ /**
10
+ * Class Ellipse
11
+ * @package Grafika
12
+ */
13
+ class Ellipse extends Base implements DrawingObjectInterface
14
+ {
15
+
16
+ /**
17
+ * TODO: Anti-aliased curves
18
+ * @param ImageInterface $image
19
+ * @return ImageInterface
20
+ */
21
+ public function draw($image)
22
+ {
23
+
24
+ list($x, $y) = $this->pos;
25
+ $left = $x + $this->width / 2;
26
+ $top = $y + $this->height / 2;
27
+
28
+ if( null !== $this->fillColor ){
29
+ list($r, $g, $b, $alpha) = $this->fillColor->getRgba();
30
+ $fillColorResource = imagecolorallocatealpha($image->getCore(), $r, $g, $b, Editor::gdAlpha($alpha));
31
+ imagefilledellipse($image->getCore(), $left, $top, $this->width, $this->height, $fillColorResource);
32
+ }
33
+ // Create borders. It will be placed on top of the filled ellipse (if present)
34
+ if ( 0 < $this->getBorderSize() and null !== $this->borderColor) { // With border > 0 AND borderColor !== null
35
+ list($r, $g, $b, $alpha) = $this->borderColor->getRgba();
36
+ $borderColorResource = imagecolorallocatealpha($image->getCore(), $r, $g, $b, Editor::gdAlpha($alpha));
37
+ imageellipse($image->getCore(), $left, $top, $this->width, $this->height, $borderColorResource);
38
+ }
39
+
40
+ return $image;
41
+ }
42
+ }
src/CycloneSlider/Grafika/Gd/DrawingObject/Line.php ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace CycloneSlider\Grafika\Gd\DrawingObject;
3
+
4
+ use CycloneSlider\Grafika\DrawingObject\Line as Base;
5
+ use CycloneSlider\Grafika\DrawingObjectInterface;
6
+ use CycloneSlider\Grafika\Gd\Image;
7
+
8
+ /**
9
+ * Class Line
10
+ * @package Grafika
11
+ */
12
+ class Line extends Base implements DrawingObjectInterface
13
+ {
14
+
15
+ /**
16
+ * @param Image $image
17
+ *
18
+ * @return Image
19
+ */
20
+ public function draw($image)
21
+ {
22
+
23
+ list( $x1, $y1 ) = $this->point1;
24
+ list( $x2, $y2 ) = $this->point2;
25
+ list( $r, $g, $b ) = $this->color->getRgb();
26
+ $color = imagecolorallocate( $image->getCore(), $r, $g, $b );
27
+ if ( function_exists( 'imageantialias' ) ) { // Not available on some if PHP is not precompiled with it even if GD is enabled
28
+ imageantialias( $image->getCore(), true );
29
+ }
30
+ imageline( $image->getCore(), $x1, $y1, $x2, $y2, $color );
31
+
32
+ return $image;
33
+ }
34
+
35
+
36
+ }
src/CycloneSlider/Grafika/Gd/DrawingObject/Polygon.php ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace CycloneSlider\Grafika\Gd\DrawingObject;
3
+
4
+ use CycloneSlider\Grafika\DrawingObject\Polygon as Base;
5
+ use CycloneSlider\Grafika\DrawingObjectInterface;
6
+ use CycloneSlider\Grafika\Gd\Editor;
7
+
8
+ /**
9
+ * Class Rectangle
10
+ * @package Grafika
11
+ */
12
+ class Polygon extends Base implements DrawingObjectInterface
13
+ {
14
+
15
+ public function draw($image)
16
+ {
17
+ if(function_exists('imageantialias')){
18
+ imageantialias($image->getCore(), true);
19
+ }
20
+ list($r, $g, $b, $alpha) = $this->getBorderColor()->getRgba();
21
+ $borderColorResource = imagecolorallocatealpha(
22
+ $image->getCore(), $r, $g, $b,
23
+ Editor::gdAlpha($alpha)
24
+ );
25
+
26
+ $points = $this->points();
27
+ $count = count($this->pos);
28
+
29
+
30
+ // Create filled polygon
31
+ if( null !== $this->fillColor){
32
+ list($r, $g, $b, $alpha) = $this->getFillColor()->getRgba();
33
+ $fillColorResource = imagecolorallocatealpha(
34
+ $image->getCore(), $r, $g, $b,
35
+ Editor::gdAlpha($alpha)
36
+ );
37
+ imagefilledpolygon($image->getCore(), $points,
38
+ $count,
39
+ $fillColorResource
40
+ );
41
+ }
42
+
43
+ // Create polygon borders. It will be placed on top of the filled polygon (if present)
44
+ if ( 0 < $this->getBorderSize() ) { // With border > 0
45
+ imagepolygon($image->getCore(), $points,
46
+ $count,
47
+ $borderColorResource
48
+ );
49
+ }
50
+ return $image;
51
+ }
52
+
53
+ protected function points(){
54
+ $points = array();
55
+ foreach($this->pos as $pos){
56
+ $points[] = $pos[0];
57
+ $points[] = $pos[1];
58
+ }
59
+ if( count($points) < 6 ){
60
+ throw new \Exception('Polygon needs at least 3 points.');
61
+ }
62
+ return $points;
63
+ }
64
+ }
src/CycloneSlider/Grafika/Gd/DrawingObject/QuadraticBezier.php ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace CycloneSlider\Grafika\Gd\DrawingObject;
3
+
4
+ use CycloneSlider\Grafika\DrawingObject\QuadraticBezier as Base;
5
+ use CycloneSlider\Grafika\DrawingObjectInterface;
6
+ use CycloneSlider\Grafika\Gd\Image;
7
+ use CycloneSlider\Grafika\ImageInterface;
8
+
9
+ /**
10
+ * Class QuadraticBezier
11
+ * @package Grafika
12
+ */
13
+ class QuadraticBezier extends Base implements DrawingObjectInterface
14
+ {
15
+ /**
16
+ * @link http://members.chello.at/easyfilter/bresenham.pdf
17
+ * @param ImageInterface $image
18
+ * @return Image
19
+ */
20
+ public function draw($image)
21
+ {
22
+ // Localize vars
23
+ $width = $image->getWidth();
24
+ $height = $image->getHeight();
25
+ $gd = $image->getCore();
26
+
27
+ list($x0, $y0) = $this->point1;
28
+ list($x1, $y1) = $this->control;
29
+ list($x2, $y2) = $this->point2;
30
+
31
+ $this->plot($gd, $x0, $y0, $x1, $y1, $x2, $y2);
32
+
33
+ $type = $image->getType();
34
+ $file = $image->getImageFile();
35
+ return new Image($gd, $file, $width, $height, $type); // Create new image with updated core
36
+ }
37
+
38
+ protected function plot($gd, $x0, $y0, $x1, $y1, $x2, $y2)
39
+ {
40
+ /* plot any quadratic Bezier curve */
41
+ $x = $x0 - $x1;
42
+ $y = $y0 - $y1;
43
+ $t = $x0 - 2 * $x1 + $x2; //double
44
+
45
+ if ((int)$x * ($x2 - $x1) > 0) { /* horizontal cut at P4? */
46
+ if ((int)$y * ($y2 - $y1) > 0) /* vertical cut at P6 too? */ {
47
+ if (abs(($y0 - 2 * $y1 + $y2) / $t * $x) > abs($y)) { /* which first? */
48
+ $x0 = $x2;
49
+ $x2 = $x + $x1;
50
+ $y0 = $y2;
51
+ $y2 = $y + $y1; /* swap points */
52
+ }
53
+ } /* now horizontal cut at P4 comes first */
54
+ $t = ($x0 - $x1) / $t;
55
+ $r = (1 - $t) * ((1 - $t) * $y0 + 2.0 * $t * $y1) + $t * $t * $y2; /* By(t=P4) */
56
+ $t = ($x0 * $x2 - $x1 * $x1) * $t / ($x0 - $x1); /* gradient dP4/dx=0 */
57
+ $x = floor($t + 0.5);
58
+ $y = floor($r + 0.5);
59
+ $r = ($y1 - $y0) * ($t - $x0) / ($x1 - $x0) + $y0; /* intersect P3 | P0 P1 */
60
+ $this->plotSegment($gd, $x0, $y0, $x, floor($r + 0.5), $x, $y);
61
+ $r = ($y1 - $y2) * ($t - $x2) / ($x1 - $x2) + $y2; /* intersect P4 | P1 P2 */
62
+ $x0 = $x1 = $x;
63
+ $y0 = $y;
64
+ $y1 = floor($r + 0.5); /* P0 = P4, P1 = P8 */
65
+ }
66
+ if ((int)($y0 - $y1) * ($y2 - $y1) > 0) { /* vertical cut at P6? */
67
+ $t = $y0 - 2 * $y1 + $y2;
68
+ $t = ($y0 - $y1) / $t;
69
+ $r = (1 - $t) * ((1 - $t) * $x0 + 2.0 * $t * $x1) + $t * $t * $x2; /* Bx(t=P6) */
70
+ $t = ($y0 * $y2 - $y1 * $y1) * $t / ($y0 - $y1); /* gradient dP6/dy=0 */
71
+ $x = floor($r + 0.5);
72
+ $y = floor($t + 0.5);
73
+ $r = ($x1 - $x0) * ($t - $y0) / ($y1 - $y0) + $x0; /* intersect P6 | P0 P1 */
74
+ $this->plotSegment($gd, $x0, $y0, floor($r + 0.5), $y, $x, $y);
75
+ $r = ($x1 - $x2) * ($t - $y2) / ($y1 - $y2) + $x2; /* intersect P7 | P1 P2 */
76
+ $x0 = $x;
77
+ $x1 = floor($r + 0.5);
78
+ $y0 = $y1 = $y; /* P0 = P6, P1 = P7 */
79
+ }
80
+ $this->plotSegment($gd, $x0, $y0, $x1, $y1, $x2, $y2); /* remaining part */
81
+ }
82
+
83
+ /**
84
+ * Draw an limited anti-aliased quadratic Bezier segment.
85
+ * @param $gd
86
+ * @param $x0
87
+ * @param $y0
88
+ * @param $x1
89
+ * @param $y1
90
+ * @param $x2
91
+ * @param $y2
92
+ */
93
+ protected function plotSegment($gd, $x0, $y0, $x1, $y1, $x2, $y2)
94
+ {
95
+ $sx = $x2 - $x1;
96
+ $sy = $y2 - $y1;
97
+ $xx = $x0 - $x1;
98
+ $yy = $y0 - $y1;
99
+
100
+ $cur = $xx * $sy - $yy * $sx; /* $curvature */
101
+ assert($xx * $sx <= 0 && $yy * $sy <= 0);
102
+ if ($sx * (int)$sx + $sy * (int)$sy > $xx * $xx + $yy * $yy) { /* begin with longer part */
103
+ $x2 = $x0;
104
+ $x0 = $sx + $x1;
105
+ $y2 = $y0;
106
+ $y0 = $sy + $y1;
107
+ $cur = -$cur; /* swap P0 P2 */
108
+ }
109
+ if ($cur != 0) { /* no straight line */
110
+ $xx += $sx;
111
+ $xx *= $sx = $x0 < $x2 ? 1 : -1; /* x step direction */
112
+ $yy += $sy;
113
+ $yy *= $sy = $y0 < $y2 ? 1 : -1; /* y step direction */
114
+ $xy = 2 * $xx * $yy;
115
+ $xx *= $xx;
116
+ $yy *= $yy; /* differences 2nd degree */
117
+ if ($cur * $sx * $sy < 0) { /* negat$ed $curvature? */
118
+ $xx = -$xx;
119
+ $yy = -$yy;
120
+ $xy = -$xy;
121
+ $cur = -$cur;
122
+ }
123
+ $dx = 4.0 * $sy * ($x1 - $x0) * $cur + $xx - $xy; /* differences 1st degree */
124
+ $dy = 4.0 * $sx * ($y0 - $y1) * $cur + $yy - $xy;
125
+ $xx += $xx;
126
+ $yy += $yy;
127
+ $err = $dx + $dy + $xy; /* $error 1st step */
128
+ do {
129
+ $cur = min($dx + $xy, -$xy - $dy);
130
+ $ed = max($dx + $xy, -$xy - $dy); /* approximate $error distance */
131
+ $ed += 2 * $ed * $cur * $cur / (4 * $ed * $ed + $cur * $cur);
132
+ $this->setPixel($gd, $x0, $y0, abs($err - $dx - $dy - $xy) / $ed); /* plot $curve */
133
+ if ($x0 == $x2 || $y0 == $y2) {
134
+ break;
135
+ } /* $curve finish$ed */
136
+ $x1 = $x0;
137
+ $cur = $dx - $err;
138
+ $y1 = 2 * $err + $dy < 0;
139
+ if (2 * $err + $dx > 0) { /* x step */
140
+ if ($err - $dy < $ed) {
141
+ $this->setPixel($gd, $x0, $y0 + $sy, abs($err - $dy) / $ed);
142
+ }
143
+ $x0 += $sx;
144
+ $dx -= $xy;
145
+ $err += $dy += $yy;
146
+ }
147
+ if ($y1) { /* y step */
148
+ if ($cur < $ed) {
149
+ $this->setPixel($gd, $x1 + $sx, $y0, abs($cur) / $ed);
150
+ }
151
+ $y0 += $sy;
152
+ $dy -= $xy;
153
+ $err += $dx += $xx;
154
+ }
155
+ } while ($dy < $dx); /* gradient negates -> close curves */
156
+ }
157
+ $this->plotLine($gd, $x0, $y0, $x2, $y2); /* plot remaining needle to end */
158
+ }
159
+
160
+ protected function plotLine($gd, $x0, $y0, $x1, $y1)
161
+ {
162
+ $dx = abs($x1 - $x0);
163
+ $sx = $x0 < $x1 ? 1 : -1;
164
+ $dy = -abs($y1 - $y0);
165
+ $sy = $y0 < $y1 ? 1 : -1;
166
+ $err = $dx + $dy;
167
+
168
+ $ed = $dx - $dy == 0 ? 1 : sqrt((float)$dx * $dx + (float)$dy * $dy);
169
+ for (; ;) { /* pixel loop */
170
+ $this->setPixel($gd, $x0, $y0, abs($err - $dx - $dy) / $ed);
171
+ $e2 = $err;
172
+ $x2 = $x0;
173
+ if (2 * $e2 + $dx >= 0) { /* x step */
174
+ if ($x0 == $x1) {
175
+ break;
176
+ }
177
+ if ($e2 - $dy < $ed) {
178
+ $this->setPixel($gd, $x0, $y0 + $sy, ($e2 - $dy) / $ed);
179
+ }
180
+ $err += $dy;
181
+ $x0 += $sx;
182
+ }
183
+ if (2 * $e2 + $dy <= 0) { /* y step */
184
+ if ($y0 == $y1) {
185
+ break;
186
+ }
187
+ if ($dx - $e2 < $ed) {
188
+ $this->setPixel($gd, $x2 + $sx, $y0, ($dx - $e2) / $ed);
189
+ }
190
+ $err += $dx;
191
+ $y0 += $sy;
192
+ }
193
+ }
194
+ }
195
+
196
+ /**
197
+ * @param resource $gd
198
+ * @param int $x
199
+ * @param int $y
200
+ * @param float $ar Alpha ratio
201
+ */
202
+ protected function setPixel($gd, $x, $y, $ar)
203
+ {
204
+ list($r, $g, $b) = $this->color->getRgb();
205
+ $c = imagecolorallocatealpha($gd, $r, $g, $b, 127 * $ar);
206
+ imagesetpixel($gd, $x, $y, $c);
207
+ }
208
+ }
src/CycloneSlider/Grafika/Gd/DrawingObject/Rectangle.php ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace CycloneSlider\Grafika\Gd\DrawingObject;
3
+
4
+ use CycloneSlider\Grafika\DrawingObject\Rectangle as Base;
5
+ use CycloneSlider\Grafika\DrawingObjectInterface;
6
+ use CycloneSlider\Grafika\Gd\Editor;
7
+
8
+ /**
9
+ * Class Rectangle
10
+ * @package Grafika
11
+ */
12
+ class Rectangle extends Base implements DrawingObjectInterface
13
+ {
14
+
15
+ public function draw($image)
16
+ {
17
+ $x1 = $this->pos[0];
18
+ $x2 = $x1 + $this->getWidth();
19
+ $y1 = $this->pos[1];
20
+ $y2 = $y1 + $this->getHeight();
21
+
22
+ if( null !== $this->fillColor ){
23
+ list($r, $g, $b, $alpha) = $this->fillColor->getRgba();
24
+ $fillColorResource = imagecolorallocatealpha($image->getCore(), $r, $g, $b, Editor::gdAlpha($alpha));
25
+ imagefilledrectangle($image->getCore(), $x1, $y1, $x2, $y2, $fillColorResource);
26
+ }
27
+ // Create borders. It will be placed on top of the filled rectangle (if present)
28
+ if ( 0 < $this->getBorderSize() and null !== $this->borderColor) { // With border > 0 AND borderColor !== null
29
+ list($r, $g, $b, $alpha) = $this->borderColor->getRgba();
30
+ $borderColorResource = imagecolorallocatealpha($image->getCore(), $r, $g, $b, Editor::gdAlpha($alpha));
31
+ imagerectangle($image->getCore(), $x1, $y1, $x2, $y2, $borderColorResource);
32
+ }
33
+
34
+ return $image;
35
+ }
36
+ }
src/CycloneSlider/Grafika/Gd/Editor.php ADDED
@@ -0,0 +1,1115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace CycloneSlider\Grafika\Gd;
4
+
5
+ use CycloneSlider\Grafika\DrawingObjectInterface;
6
+ use CycloneSlider\Grafika\EditorInterface;
7
+ use CycloneSlider\Grafika\EffectInterface;
8
+ use CycloneSlider\Grafika\Gd\DrawingObject\CubicBezier;
9
+ use CycloneSlider\Grafika\Gd\DrawingObject\Ellipse;
10
+ use CycloneSlider\Grafika\Gd\DrawingObject\Line;
11
+ use CycloneSlider\Grafika\Gd\DrawingObject\Polygon;
12
+ use CycloneSlider\Grafika\Gd\DrawingObject\QuadraticBezier;
13
+ use CycloneSlider\Grafika\Gd\DrawingObject\Rectangle;
14
+ use CycloneSlider\Grafika\Gd\Effect\Dither;
15
+ use CycloneSlider\Grafika\Grafika;
16
+ use CycloneSlider\Grafika\ImageInterface;
17
+ use CycloneSlider\Grafika\ImageType;
18
+ use CycloneSlider\Grafika\Color;
19
+
20
+ /**
21
+ * GD Editor class. Uses the PHP GD library.
22
+ * @package Grafika\Gd
23
+ */
24
+ final class Editor implements EditorInterface {
25
+
26
+ /**
27
+ * @var Image Holds the image instance.
28
+ */
29
+ private $image;
30
+
31
+ /**
32
+ * Constructor
33
+ */
34
+ function __construct() {
35
+ $this->image = null;
36
+ }
37
+
38
+ /**
39
+ * @param EffectInterface $effect
40
+ *
41
+ * @return $this
42
+ */
43
+ public function apply( $effect ){
44
+ $this->image = $effect->apply( $this->image );
45
+ return $this;
46
+ }
47
+
48
+ /**
49
+ * Creates a cubic bezier. Cubic bezier has 2 control points.
50
+ * @param array $point1 Array of X and Y value for start point.
51
+ * @param array $control1 Array of X and Y value for control point 1.
52
+ * @param array $control2 Array of X and Y value for control point 2.
53
+ * @param array $point2 Array of X and Y value for end point.
54
+ * @param Color|string $color Color of the curve. Accepts hex string or a Color object. Defaults to black.
55
+ * @return Editor
56
+ */
57
+ public function bezierCubic($point1, $control1, $control2, $point2, $color = '#000000') {
58
+ if(is_string($color)){
59
+ $color = new Color($color);
60
+ }
61
+ $obj = new CubicBezier($point1, $control1, $control2, $point2, $color);
62
+ return $this->draw($obj);
63
+ }
64
+
65
+ /**
66
+ * Creates a quadratic bezier. Quadratic bezier has 1 control point.
67
+ * @param array $point1 Array of X and Y value for start point.
68
+ * @param array $control Array of X and Y value for control point.
69
+ * @param array $point2 Array of X and Y value for end point.
70
+ * @param Color|string $color Color of the curve. Accepts hex string or a Color object. Defaults to black.
71
+ * @return Editor
72
+ */
73
+ public function bezierQuad($point1, $control, $point2, $color = '#000000') {
74
+ if(is_string($color)){
75
+ $color = new Color($color);
76
+ }
77
+ $obj = new QuadraticBezier($point1, $control, $point2, $color);
78
+ return $this->draw($obj);
79
+ }
80
+
81
+ /**
82
+ * Create a blank image given width and height.
83
+ *
84
+ * @param int $width Width of image in pixels.
85
+ * @param int $height Height of image in pixels.
86
+ *
87
+ * @return self
88
+ */
89
+ public function blank( $width, $height ) {
90
+ $this->image = Image::createBlank( $width, $height );
91
+
92
+ return $this;
93
+ }
94
+
95
+ /**
96
+ * Compare two images and returns a hamming distance. A value of 0 indicates a likely similar picture. A value between 1 and 10 is potentially a variation. A value greater than 10 is likely a different image.
97
+ * @param ImageInterface|string $image1
98
+ * @param ImageInterface|string $image2
99
+ * @return int Hamming distance. Note: This breaks the chain if you are doing fluent api calls as it does not return an Editor.
100
+ * @throws \Exception
101
+ */
102
+ public function compare( $image1, $image2 ){
103
+
104
+ if ( is_string( $image1 ) ) { // If string passed, turn it into a Image object
105
+ $image1 = Image::createFromFile( $image1 );
106
+ }
107
+
108
+ if ( is_string( $image2 ) ) { // If string passed, turn it into a Image object
109
+ $image2 = Image::createFromFile( $image2 );
110
+ }
111
+
112
+ $bin1 = $this->_differenceHash($image1);
113
+ $bin2 = $this->_differenceHash($image2);
114
+ $str1 = str_split($bin1);
115
+ $str2 = str_split($bin2);
116
+ $distance = 0;
117
+ foreach($str1 as $i=>$char){
118
+ if($char !== $str2[$i]){
119
+ $distance++;
120
+ }
121
+ }
122
+ return $distance;
123
+
124
+ }
125
+
126
+ /**
127
+ * Crop the image to the given dimension and position.
128
+ *
129
+ * @param int $cropWidth Crop width in pixels.
130
+ * @param int $cropHeight Crop Height in pixels.
131
+ * @param int|string $cropX The number of pixels from the left of the image. This parameter can be a number or any of the words "left", "center", "right".
132
+ * @param int|string $cropY The number of pixels from the top of the image. This parameter can be a number or any of the words "top", "center", "bottom".
133
+ *
134
+ * @return self
135
+ */
136
+ public function crop($cropWidth, $cropHeight, $cropX='center', $cropY='center') {
137
+
138
+ if(is_string($cropX)){
139
+ // Compute position from string
140
+ switch ($cropX){
141
+ case 'left':
142
+ $x = 0;
143
+ break;
144
+
145
+ case 'right':
146
+ $x = $this->image->getWidth() - $cropWidth;
147
+ break;
148
+
149
+ case 'center':
150
+ default:
151
+ $x = (int) round( ($this->image->getWidth()/2) - ($cropWidth/2) );
152
+ break;
153
+ }
154
+ } else {
155
+ $x = $cropX;
156
+ }
157
+
158
+ if(is_string($cropY)){
159
+ switch ($cropY){
160
+ case 'top':
161
+ $y = 0;
162
+ break;
163
+
164
+ case 'bottom':
165
+ $y = $this->image->getHeight() - $cropHeight;
166
+ break;
167
+
168
+ case 'center':
169
+ default:
170
+ $y = (int) round( ($this->image->getHeight()/2) - ($cropHeight/2) );
171
+ break;
172
+ }
173
+ } else {
174
+ $y = $cropY;
175
+ }
176
+
177
+ // Create blank image
178
+ $newImageResource = imagecreatetruecolor($cropWidth, $cropHeight);
179
+
180
+ // Now crop
181
+ imagecopyresampled(
182
+ $newImageResource, // Target image
183
+ $this->image->getCore(), // Source image
184
+ 0, // Target x
185
+ 0, // Target y
186
+ $x, // Src x
187
+ $y, // Src y
188
+ $cropWidth, // Target width
189
+ $cropHeight, // Target height
190
+ $cropWidth, // Src width
191
+ $cropHeight // Src height
192
+ );
193
+
194
+ // Free memory of old resource
195
+ imagedestroy( $this->image->getCore() );
196
+
197
+ // Cropped image instance
198
+ $this->image = new Image($newImageResource, $this->image->getImageFile(), $cropWidth, $cropHeight, $this->image->getType());
199
+
200
+ return $this;
201
+ }
202
+
203
+ /**
204
+ * Dither image using Floyd-Steinberg algorithm. Dithering will reduce the color to black and white and add noise.
205
+ * @return EditorInterface An instance of image editor.
206
+ */
207
+ public function dither(){
208
+ $e = new Dither();
209
+ return $this->apply($e);
210
+ }
211
+
212
+ /**
213
+ * @param DrawingObjectInterface $drawingObject
214
+ *
215
+ * @return $this
216
+ */
217
+ public function draw( $drawingObject ){
218
+ $this->image = $drawingObject->draw( $this->image );
219
+ return $this;
220
+ }
221
+
222
+ /**
223
+ * Creates an ellipse.
224
+ *
225
+ * @param int $width Width of ellipse in pixels.
226
+ * @param int $height Height of ellipse in pixels.
227
+ * @param array $pos Array containing int X and int Y position of the ellipse from top left of the canvass.
228
+ * @param int $borderSize Size of the border in pixels. Defaults to 1 pixel. Set to 0 for no border.
229
+ * @param Color|string|null $borderColor Border color. Defaults to black. Set to null for no color.
230
+ * @param Color|string|null $fillColor Fill color. Defaults to white. Set to null for no color.
231
+ *
232
+ * @return EditorInterface An instance of image editor.
233
+ */
234
+ public function ellipse($width, $height, array $pos, $borderSize = 1, $borderColor = '#000000', $fillColor = '#FFFFFF') {
235
+ if(is_string($borderColor)){
236
+ $borderColor = new Color($borderColor);
237
+ }
238
+ if(is_string($fillColor)){
239
+ $fillColor = new Color($fillColor);
240
+ }
241
+ $obj = new Ellipse($width, $height, $pos, $borderSize, $borderColor, $fillColor);
242
+ return $this->draw($obj);
243
+ }
244
+
245
+ /**
246
+ * Compare if two images are equal. It will compare if the two images are of the same width and height. If the dimensions differ, it will return false. If the dimensions are equal, it will loop through each pixels. If one of the pixel don't match, it will return false. The pixels are compared using their RGB (Red, Green, Blue) values.
247
+ *
248
+ * @param string|ImageInterface $image1 Can be an instance of Image or string containing the file system path to image.
249
+ * @param string|ImageInterface $image2 Can be an instance of Image or string containing the file system path to image.
250
+ *
251
+ * @return bool True if equals false if not. Note: This breaks the chain if you are doing fluent api calls as it does not return an Editor.
252
+ * @throws \Exception
253
+ */
254
+ public function equal( $image1, $image2 ){
255
+
256
+ if ( is_string( $image1 ) ) { // If string passed, turn it into a Image object
257
+ $image1 = Image::createFromFile( $image1 );
258
+ }
259
+
260
+ if ( is_string( $image2 ) ) { // If string passed, turn it into a Image object
261
+ $image2 = Image::createFromFile( $image2 );
262
+ }
263
+
264
+ // Check if image dimensions are equal
265
+ if($image1->getWidth() !== $image2->getWidth() or $image1->getHeight() !== $image2->getHeight()) {
266
+ return false;
267
+
268
+ } else {
269
+
270
+ // Loop using image1
271
+ for ( $y = 0; $y < $image1->getHeight(); $y ++ ) {
272
+ for ( $x = 0; $x < $image1->getWidth(); $x ++ ) {
273
+
274
+ // Get image1 pixel
275
+ $rgb1 = imagecolorat( $image1->getCore(), $x, $y );
276
+ $r1 = ($rgb1 >> 16) & 0xFF;
277
+ $g1 = ($rgb1 >> 8) & 0xFF;
278
+ $b1 = $rgb1 & 0xFF;
279
+
280
+ // Get image2 pixel
281
+ $rgb2 = imagecolorat( $image2->getCore(), $x, $y );
282
+ $r2 = ($rgb2 >> 16) & 0xFF;
283
+ $g2 = ($rgb2 >> 8) & 0xFF;
284
+ $b2 = $rgb2 & 0xFF;
285
+
286
+ // Compare pixel value
287
+ if (
288
+ $r1 !== $r2 or
289
+ $g1 !== $g2 or
290
+ $b1 !== $b2
291
+ ) {
292
+ return false;
293
+ }
294
+ }
295
+ }
296
+ }
297
+ return true;
298
+ }
299
+
300
+ /**
301
+ * Free the current image clearing resources associated with it.
302
+ */
303
+ public function free(){
304
+ if ( null !== $this->image ) {
305
+ if( null !== $this->image->getCore() ) {
306
+ imagedestroy( $this->image->getCore() );
307
+ }
308
+ } else {
309
+ $this->image = null;
310
+ }
311
+ }
312
+
313
+ /**
314
+ * Fill entire image with color.
315
+ *
316
+ * @param Color $color Color object
317
+ * @param int $x X-coordinate of start point
318
+ * @param int $y Y-coordinate of start point
319
+ *
320
+ * @return self
321
+ */
322
+ public function fill( $color, $x = 0, $y = 0 ) {
323
+
324
+ $this->_imageCheck();
325
+
326
+ list( $r, $g, $b, $alpha ) = $color->getRgba();
327
+
328
+ $colorResource = imagecolorallocatealpha( $this->image->getCore(), $r, $g, $b,
329
+ $this->gdAlpha( $alpha ) );
330
+ imagefill( $this->image->getCore(), $x, $y, $colorResource );
331
+
332
+ return $this;
333
+ }
334
+
335
+ /**
336
+ * Converts image to grayscale.
337
+ *
338
+ * @return $this
339
+ */
340
+ public function grayscale() {
341
+ $this->_imageCheck();
342
+
343
+ imagefilter( $this->image->getCore(), IMG_FILTER_GRAYSCALE );
344
+
345
+ return $this;
346
+ }
347
+
348
+ /**
349
+ * Alias for grayscale. They are the same.
350
+ *
351
+ * @return $this
352
+ */
353
+ public function greyscale() {
354
+ return $this->grayscale();
355
+ }
356
+
357
+ /**
358
+ * Get image instance.
359
+ *
360
+ * @return Image
361
+ */
362
+ public function getImage() {
363
+ return $this->image;
364
+ }
365
+
366
+ /**
367
+ * Checks the PHP install if the editor is available.
368
+ *
369
+ * @return bool True if available false if not.
370
+ */
371
+ public function isAvailable() {
372
+ if ( false === extension_loaded( 'gd' ) || false === function_exists( 'gd_info' ) ) {
373
+ return false;
374
+ }
375
+
376
+ // On some setups GD library does not provide imagerotate()
377
+ if ( ! function_exists( 'imagerotate' ) ) {
378
+
379
+ return false;
380
+ }
381
+
382
+ return true;
383
+ }
384
+
385
+ /**
386
+ * Creates a line.
387
+ *
388
+ * @param array $point1 Array containing int X and int Y position of the starting point.
389
+ * @param array $point2 Array containing int X and int Y position of the starting point.
390
+ * @param int $thickness Thickness in pixel. Note: This is currently ignored in GD editor and falls back to 1.
391
+ * @param Color|string $color Color of the line. Defaults to black.
392
+ * @return Editor
393
+ */
394
+ public function line(array $point1, array $point2, $thickness = 1, $color = '#000000') {
395
+ if(is_string($color)){
396
+ $color = new Color($color);
397
+ }
398
+ $obj = new Line($point1, $point2, $thickness, $color);
399
+ return $this->draw($obj);
400
+ }
401
+
402
+ /**
403
+ * Sets the image to the specified opacity level where 1.0 is fully opaque and 0.0 is fully transparent.
404
+ * Warning: This function loops thru each pixel manually which can be slow. Use sparingly.
405
+ *
406
+ * @param float $opacity
407
+ *
408
+ * @return self
409
+ * @throws \Exception
410
+ */
411
+ public function opacity( $opacity ){
412
+
413
+ $this->_imageCheck();
414
+
415
+ // Bounds checks
416
+ $opacity = ($opacity > 1) ? 1 : $opacity;
417
+ $opacity = ($opacity < 0) ? 0 : $opacity;
418
+
419
+ for($y = 0; $y < $this->image->getHeight(); $y++){
420
+ for($x = 0; $x < $this->image->getWidth(); $x++){
421
+ $rgb = imagecolorat($this->image->getCore(), $x, $y);
422
+ $alpha = ($rgb >> 24) & 0x7F; // 127 in hex. These are binary operations.
423
+ $r = ($rgb >> 16) & 0xFF;
424
+ $g = ($rgb >> 8) & 0xFF;
425
+ $b = $rgb & 0xFF;
426
+
427
+ // Reverse alpha values from 127-0 (transparent to opaque) to 0-127 for easy math
428
+ // Previously: 0 = opaque, 127 = transparent.
429
+ // Now: 0 = transparent, 127 = opaque
430
+ $reverse = 127 - $alpha;
431
+ $reverse = round($reverse * $opacity);
432
+
433
+ if( $alpha < 127 ) { // Process non transparent pixels only
434
+ imagesetpixel( $this->image->getCore(), $x, $y, imagecolorallocatealpha( $this->image->getCore(), $r, $g, $b, 127 - $reverse ) );
435
+ }
436
+ }
437
+ }
438
+
439
+ return $this;
440
+ }
441
+
442
+ /**
443
+ * Opens an image file for manipulation specified by $target.
444
+ *
445
+ * @param mixed $target Can be an instance of Image or a string containing file system path to the image.
446
+ *
447
+ * @return Editor
448
+ * @throws \Exception
449
+ */
450
+ public function open( $target ) {
451
+ if( $target instanceof ImageInterface) {
452
+ $this->openImage( $target );
453
+ } else if (is_string($target)){
454
+ $this->openFile( $target );
455
+ } else {
456
+ throw new \Exception( 'Could not open image.' );
457
+ }
458
+
459
+ return $this;
460
+ }
461
+
462
+ /**
463
+ * Open an image by passing an instance of Image.
464
+ *
465
+ * @param ImageInterface $image
466
+ *
467
+ * @return $this
468
+ */
469
+ public function openImage( $image ){
470
+ $this->image = $image;
471
+
472
+ return $this;
473
+ }
474
+
475
+ /**
476
+ * Open an image by passing a file system path.
477
+ *
478
+ * @param string $file A full path to the image in the file system.
479
+ *
480
+ * @return $this
481
+ * @throws \Exception
482
+ */
483
+ public function openFile( $file ){
484
+ $this->image = Image::createFromFile( $file );
485
+
486
+ return $this;
487
+ }
488
+
489
+ /**
490
+ * Overlay an image on top of the current image.
491
+ *
492
+ * @param Image|string $overlay Can be a string containing a file path of the image to overlay or an Image object.
493
+ * @param string|int $xPos Horizontal position of image. Can be 'left','center','right' or integer number. Defaults to 'center'.
494
+ * @param string|int $yPos Vertical position of image. Can be 'top', 'center','bottom' or integer number. Defaults to 'center'.
495
+ * @param null $width
496
+ * @param null $height
497
+ *
498
+ * @return Editor
499
+ * @throws \Exception
500
+ */
501
+ public function overlay( $overlay, $xPos = 'center', $yPos = 'center', $width = null, $height = null ) {
502
+
503
+ $this->_imageCheck();
504
+
505
+ if ( is_string( $overlay ) ) { // If string passed, turn it into a Image object
506
+ $overlay = Image::createFromFile( $overlay );
507
+ }
508
+
509
+ // Resize overlay
510
+ if($width and $height){
511
+
512
+ $overlayWidth = $overlay->getWidth();
513
+ $overlayHeight = $overlay->getHeight();
514
+
515
+ if(is_numeric($width)){
516
+ $overlayWidth = (int) $width;
517
+ } else {
518
+ $percent = strpos( $width, '%' );
519
+ if( false !== $percent){
520
+ $overlayWidth = intval( $width ) / 100 * $this->image->getWidth();
521
+ }
522
+ }
523
+
524
+ if(is_numeric($height)){
525
+ $overlayHeight = (int) $height;
526
+ } else {
527
+ $percent = strpos( $height, '%' );
528
+ if( false !== $percent){
529
+ $overlayHeight = intval( $height ) / 100 * $this->image->getHeight();
530
+ }
531
+ }
532
+
533
+ $editor = new Editor();
534
+ $editor->setImage($overlay);
535
+ $editor->resizeFit( $overlayWidth, $overlayHeight );
536
+ $overlay = $editor->getImage();
537
+
538
+ //$overlay->setImageResource( $editor->getImage()->getImageResource() );
539
+ }
540
+
541
+ //$x = $y = 0;
542
+
543
+ if ( is_string( $xPos ) ) {
544
+ // Compute position from string
545
+ switch ( $xPos ) {
546
+ case 'left':
547
+ $x = 0;
548
+ break;
549
+
550
+ case 'right':
551
+ $x = $this->image->getWidth() - $overlay->getWidth();
552
+ break;
553
+
554
+ case 'center':
555
+ default:
556
+ $x = (int) round( ( $this->image->getWidth() / 2 ) - ( $overlay->getWidth() / 2 ) );
557
+ break;
558
+ }
559
+ } else {
560
+ $x = $xPos;
561
+ }
562
+
563
+ if ( is_string( $yPos ) ) {
564
+ switch ( $yPos ) {
565
+ case 'top':
566
+ $y = 0;
567
+ break;
568
+
569
+ case 'bottom':
570
+ $y = $this->image->getHeight() - $overlay->getHeight();
571
+ break;
572
+
573
+ case 'center':
574
+ default:
575
+ $y = (int) round( ( $this->image->getHeight() / 2 ) - ( $overlay->getHeight() / 2 ) );
576
+ break;
577
+ }
578
+ } else {
579
+ $y = $yPos;
580
+ }
581
+
582
+ imagecopyresampled(
583
+ $this->image->getCore(), // Base image
584
+ $overlay->getCore(), // Overlay
585
+ (int) $x, // Overlay x position
586
+ (int) $y, // Overlay y position
587
+ 0,
588
+ 0,
589
+ $overlay->getWidth(), // Overlay final width
590
+ $overlay->getHeight(), // Overlay final height
591
+ $overlay->getWidth(), // Overlay source width
592
+ $overlay->getHeight() // Overlay source height
593
+ );
594
+
595
+ return $this;
596
+
597
+ }
598
+
599
+ /**
600
+ * Creates a polygon.
601
+ *
602
+ * @param array $points Array of all X and Y positions. Must have at least three positions.
603
+ * @param int $borderSize Size of the border in pixels. Defaults to 1 pixel. Set to 0 for no border.
604
+ * @param Color|string|null $borderColor Border color. Defaults to black. Set to null for no color.
605
+ * @param Color|string|null $fillColor Fill color. Defaults to white. Set to null for no color.
606
+ *
607
+ * @return EditorInterface An instance of image editor.
608
+ */
609
+ public function polygon($points, $borderSize = 1, $borderColor = '#000000', $fillColor = '#FFFFFF'){
610
+ if(is_string($borderColor)){
611
+ $borderColor = new Color($borderColor);
612
+ }
613
+ if(is_string($fillColor)){
614
+ $fillColor = new Color($fillColor);
615
+ }
616
+ $obj = new Polygon($points, $borderSize, $borderColor, $fillColor);
617
+ return $this->draw($obj);
618
+ }
619
+
620
+ /**
621
+ * Creates a rectangle.
622
+ * @param int $width Width of rectangle in pixels.
623
+ * @param int $height Height in pixels.
624
+ * @param array $pos Array of X and Y position. X is the distance in pixels from the left of the canvass to the left of the rectangle. Y is the distance from the top of the canvass to the top of the rectangle. Defaults to array(0,0).
625
+ * @param int $borderSize Size of the border in pixels. Defaults to 1 pixel. Set to 0 for no border.
626
+ * @param Color|string|null $borderColor Border color. Defaults to black. Set to null for no color.
627
+ * @param Color|string|null $fillColor Fill color. Defaults to white. Set to null for no color.
628
+ * @return Editor
629
+ */
630
+ public function rectangle($width, $height, $pos = array(0, 0), $borderSize = 1, $borderColor = '#000000', $fillColor = '#FFFFFF') {
631
+ if(is_string($borderColor)){
632
+ $borderColor = new Color($borderColor);
633
+ }
634
+ if(is_string($fillColor)){
635
+ $fillColor = new Color($fillColor);
636
+ }
637
+ $obj = new Rectangle($width, $height, $pos, $borderSize, $borderColor, $fillColor);
638
+ return $this->draw($obj);
639
+ }
640
+
641
+
642
+ /**
643
+ * Wrapper function for the resizeXXX family of functions. Resize image given width, height and mode.
644
+ *
645
+ * @param int $newWidth Width in pixels.
646
+ * @param int $newHeight Height in pixels.
647
+ * @param string $mode Resize mode. Possible values: "exact", "exactHeight", "exactWidth", "fill", "fit".
648
+ *
649
+ * @return Editor
650
+ * @throws \Exception
651
+ */
652
+ public function resize( $newWidth, $newHeight, $mode='fit' ){
653
+ /*
654
+ * Resize formula:
655
+ * ratio = w / h
656
+ * h = w / ratio
657
+ * w = h * ratio
658
+ */
659
+ switch ($mode){
660
+ case 'exact':
661
+ $this->resizeExact( $newWidth, $newHeight );
662
+ break;
663
+ case 'fill':
664
+ $this->resizeFill($newWidth, $newHeight);
665
+ break;
666
+ case 'exactWidth':
667
+ $this->resizeExactWidth( $newWidth );
668
+ break;
669
+ case 'exactHeight':
670
+ $this->resizeExactHeight( $newHeight );
671
+ break;
672
+ case 'fit':
673
+ $this->resizeFit($newWidth, $newHeight);
674
+ break;
675
+ default:
676
+ throw new \Exception( sprintf('Invalid resize mode "%s".', $mode) );
677
+ }
678
+
679
+ return $this;
680
+ }
681
+
682
+ /**
683
+ * Resize image to exact dimensions ignoring aspect ratio. Useful if you want to force exact width and height.
684
+ *
685
+ * @param int $newWidth Width in pixels.
686
+ * @param int $newHeight Height in pixels.
687
+ *
688
+ * @return self
689
+ */
690
+ public function resizeExact( $newWidth, $newHeight ){
691
+
692
+ $this->_resize( $newWidth, $newHeight );
693
+
694
+ return $this;
695
+ }
696
+
697
+ /**
698
+ * Resize image to exact height. Width is auto calculated. Useful for creating row of images with the same height.
699
+ *
700
+ * @param int $newHeight Height in pixels.
701
+ *
702
+ * @return self
703
+ */
704
+ public function resizeExactHeight( $newHeight ){
705
+
706
+ $width = $this->image->getWidth();
707
+ $height = $this->image->getHeight();
708
+ $ratio = $width / $height;
709
+
710
+ $resizeHeight = $newHeight;
711
+ $resizeWidth = $newHeight * $ratio;
712
+
713
+ $this->_resize( $resizeWidth, $resizeHeight );
714
+
715
+ return $this;
716
+ }
717
+
718
+ /**
719
+ * Resize image to exact width. Height is auto calculated. Useful for creating column of images with the same width.
720
+ *
721
+ * @param int $newWidth Width in pixels.
722
+ *
723
+ * @return self
724
+ */
725
+ public function resizeExactWidth( $newWidth ){
726
+
727
+ $width = $this->image->getWidth();
728
+ $height = $this->image->getHeight();
729
+ $ratio = $width / $height;
730
+
731
+ $resizeWidth = $newWidth;
732
+ $resizeHeight = round($newWidth / $ratio);
733
+
734
+ $this->_resize( $resizeWidth, $resizeHeight );
735
+
736
+ return $this;
737
+ }
738
+
739
+ /**
740
+ * Resize image to fill all the space in the given dimension. Excess parts are cropped.
741
+ *
742
+ * @param int $newWidth Width in pixels.
743
+ * @param int $newHeight Height in pixels.
744
+ *
745
+ * @return self
746
+ */
747
+ public function resizeFill( $newWidth, $newHeight ){
748
+ $width = $this->image->getWidth();
749
+ $height = $this->image->getHeight();
750
+ $ratio = $width / $height;
751
+
752
+ // Base optimum size on new width
753
+ $optimumWidth = $newWidth;
754
+ $optimumHeight = round($newWidth / $ratio);
755
+
756
+ if( ($optimumWidth < $newWidth) or ($optimumHeight < $newHeight) ){ // Oops, where trying to fill and there are blank areas
757
+ // So base optimum size on height instead
758
+ $optimumWidth = $newHeight * $ratio;
759
+ $optimumHeight = $newHeight;
760
+ }
761
+
762
+ $this->_resize( $optimumWidth, $optimumHeight);
763
+ $this->crop( $newWidth, $newHeight ); // Trim excess parts
764
+
765
+ return $this;
766
+ }
767
+
768
+ /**
769
+ * Resize image to fit inside the given dimension. No part of the image is lost.
770
+ *
771
+ * @param int $newWidth Width in pixels.
772
+ * @param int $newHeight Height in pixels.
773
+ *
774
+ * @return self
775
+ */
776
+ public function resizeFit( $newWidth, $newHeight ){
777
+
778
+ $width = $this->image->getWidth();
779
+ $height = $this->image->getHeight();
780
+ $ratio = $width / $height;
781
+
782
+ // Try basing it on width first
783
+ $resizeWidth = $newWidth;
784
+ $resizeHeight = round($newWidth / $ratio);
785
+
786
+ if( ( $resizeWidth > $newWidth ) or ( $resizeHeight > $newHeight ) ){ // Oops, either with or height does not fit
787
+ // So base on height instead
788
+ $resizeHeight = $newHeight;
789
+ $resizeWidth = $newHeight * $ratio;
790
+ }
791
+
792
+ $this->_resize( $resizeWidth, $resizeHeight );
793
+
794
+ return $this;
795
+ }
796
+
797
+ /**
798
+ * Rotate an image counter-clockwise.
799
+ *
800
+ * @param int $angle The angle in degrees.
801
+ * @param Color|null $color The Color object containing the background color.
802
+ *
803
+ * @return EditorInterface An instance of image editor.
804
+ */
805
+ public function rotate( $angle, $color = null ){
806
+
807
+ $this->_imageCheck();
808
+
809
+ $color = ($color !== null) ? $color : new Color('#000000');
810
+ list( $r, $g, $b, $alpha ) = $color->getRgba();
811
+
812
+ imagerotate($this->image->getCore(), $angle, imagecolorallocatealpha( $this->image->getCore(), $r, $g, $b, $alpha ) );
813
+
814
+ return $this;
815
+ }
816
+
817
+ /**
818
+ * Save the image to an image format.
819
+ *
820
+ * @param string $file File path where to save the image.
821
+ * @param null|string $type Type of image. Can be null, "GIF", "PNG", or "JPEG".
822
+ * @param null|string $quality Quality of image. Applies to JPEG only. Accepts number 0 - 100 where 0 is lowest and 100 is the highest quality. Or null for default.
823
+ * @param bool|false $interlace Set to true for progressive JPEG. Applies to JPEG only.
824
+ * @param int $permission Default permission when creating non-existing target directory.
825
+ *
826
+ * @return Editor
827
+ * @throws \Exception
828
+ */
829
+ public function save( $file, $type = null, $quality = null, $interlace = false, $permission = 0755 ){
830
+
831
+ $this->_imageCheck();
832
+
833
+ if ( null === $type ) {
834
+
835
+ $type = $this->_getImageTypeFromFileName( $file ); // Null given, guess type from file extension
836
+ if ( ImageType::UNKNOWN === $type ) {
837
+ $type = $this->image->getType(); // 0 result, use original image type
838
+ }
839
+ }
840
+
841
+ $targetDir = dirname( $file ); // $file's directory
842
+ if( false === is_dir( $targetDir ) ){ // Check if $file's directory exist
843
+ // Create and set default perms to 0755
844
+ if( !mkdir( $targetDir, $permission, true ) ){
845
+ throw new \Exception( sprintf('Cannot create %s', $targetDir) );
846
+ }
847
+ }
848
+
849
+ switch ( strtoupper($type) ) {
850
+ case ImageType::GIF :
851
+ imagegif( $this->image->getCore(), $file );
852
+ break;
853
+
854
+ case ImageType::PNG :
855
+ $quality = ( $quality === null ) ? 0 : $quality;
856
+ $quality = ( $quality > 9 ) ? 9 : $quality;
857
+ $quality = ( $quality < 0 ) ? 0 : $quality;
858
+ imagepng( $this->image->getCore(), $file, $quality );
859
+ break;
860
+
861
+ default: // Defaults to jpeg
862
+ $quality = ( $quality === null ) ? 100 : $quality;
863
+ $quality = ( $quality > 100 ) ? 100 : $quality;
864
+ $quality = ( $quality < 0 ) ? 0 : $quality;
865
+ imageinterlace( $this->image->getCore(), $interlace );
866
+ imagejpeg( $this->image->getCore(), $file, $quality );
867
+ }
868
+
869
+ return $this;
870
+ }
871
+
872
+ /**
873
+ * Set image instance.
874
+ *
875
+ * @param Image $image
876
+ */
877
+ public function setImage( $image ) {
878
+ $this->image = $image;
879
+ }
880
+
881
+ /**
882
+ * Write text to image.
883
+ *
884
+ * @param string $text The text to be written.
885
+ * @param int $size The font size. Defaults to 12.
886
+ * @param int $x The distance from the left edge of the image to the left of the text. Defaults to 0.
887
+ * @param int $y The distance from the top edge of the image to the top of the text. Defaults to 12 (equal to font size) so that the text is placed within the image.
888
+ * @param Color $color The Color object. Default text color is black.
889
+ * @param string $font Full path to font file. If blank, will default to Liberation Sans font.
890
+ * @param int $angle Angle of text from 0 - 359. Defaults to 0.
891
+ *
892
+ * @return EditorInterface
893
+ * @throws \Exception
894
+ */
895
+ public function text( $text, $size = 12, $x = 0, $y = 0, $color = null, $font = '', $angle = 0 ) {
896
+
897
+ $this->_imageCheck();
898
+
899
+ $y += $size;
900
+
901
+ $color = ($color !== null) ? $color : new Color('#000000');
902
+ $font = ($font !== '') ? $font : Grafika::fontsDir().DIRECTORY_SEPARATOR.'LiberationSans-Regular.ttf';
903
+
904
+ list( $r, $g, $b, $alpha ) = $color->getRgba();
905
+
906
+ $colorResource = imagecolorallocatealpha(
907
+ $this->image->getCore(),
908
+ $r, $g, $b,
909
+ $this->gdAlpha( $alpha )
910
+ );
911
+
912
+ imagettftext(
913
+ $this->image->getCore(),
914
+ $size,
915
+ $angle,
916
+ $x,
917
+ $y,
918
+ $colorResource,
919
+ $font,
920
+ $text
921
+ );
922
+
923
+ return $this;
924
+ }
925
+
926
+ /**
927
+ * Get difference hash of image.
928
+ * Algorithm:
929
+ * Reduce size. The fastest way to remove high frequencies and detail is to shrink the image. In this case, shrink it to 9x8 so that there are 72 total pixels.
930
+ * Reduce color. Convert the image to a grayscale picture. This changes the hash from 72 pixels to a total of 72 colors.
931
+ * Compute the difference. The algorithm works on the difference between adjacent pixels. This identifies the relative gradient direction. In this case, the 9 pixels per row yields 8 differences between adjacent pixels. Eight rows of eight differences becomes 64 bits.
932
+ * Assign bits. Each bit is simply set based on whether the left pixel is brighter than the right pixel.
933
+ *
934
+ * http://www.hackerfactor.com/blog/index.php?/archives/529-Kind-of-Like-That.html
935
+ * @param Image $image
936
+ * @return string
937
+ */
938
+ private function _differenceHash($image)
939
+ {
940
+
941
+ $width = 9;
942
+ $height = 8;
943
+
944
+ $editor = new Editor();
945
+ $editor->setImage($image);
946
+ $editor->resizeExact( $width, $height); // Resize to exactly 9x8
947
+ $gd = $editor->getImage()->getCore();
948
+
949
+ // Build hash
950
+ $hash = '';
951
+ for ($y = 0; $y < $height; $y++) {
952
+ // Get the pixel value for the leftmost pixel.
953
+ $rgba = imagecolorat($gd, 0, $y);
954
+ $r = ($rgba >> 16) & 0xFF;
955
+ $g = ($rgba >> 8) & 0xFF;
956
+ $b = $rgba & 0xFF;
957
+
958
+ $left = floor(($r + $g + $b) / 3);
959
+ for ($x = 1; $x < $width; $x++) {
960
+ // Get the pixel value for each pixel starting from position 1.
961
+ $rgba = imagecolorat($gd, $x, $y);
962
+ $r = ($rgba >> 16) & 0xFF;
963
+ $g = ($rgba >> 8) & 0xFF;
964
+ $b = $rgba & 0xFF;
965
+ $right = floor(($r + $g + $b) / 3);
966
+ // Each hash bit is set based on whether the left pixel is brighter than the right pixel.
967
+ if ($left > $right) {
968
+ $hash .= '1';
969
+ } else {
970
+ $hash .= '0';
971
+ }
972
+ // Prepare the next loop.
973
+ $left = $right;
974
+ }
975
+ }
976
+
977
+ return $hash;
978
+ }
979
+
980
+ // TODO: Maybe detach hashes from editor and move to their own classes
981
+ // private function _averageHash($image)
982
+ // {
983
+ // // Resize the image.
984
+ // $width = 8;
985
+ // $height = 8;
986
+ //
987
+ // $editor = new Editor();
988
+ // $editor->setImage($image);
989
+ // $editor->resizeExact( $width, $height); // Resize to exactly 9x8
990
+ // $gd = $editor->getImage()->getCore();
991
+ //
992
+ // // Create an array of greyscale pixel values.
993
+ // $pixels = array();
994
+ // for ($y = 0; $y < $height; $y++) {
995
+ // for ($x = 0; $x < $width; $x++) {
996
+ // $rgba = imagecolorat($gd, $x, $y);
997
+ // $r = ($rgba >> 16) & 0xFF;
998
+ // $g = ($rgba >> 8) & 0xFF;
999
+ // $b = $rgba & 0xFF;
1000
+ //
1001
+ // $pixels[] = floor(($r + $g + $b) / 3); // Gray
1002
+ // }
1003
+ // }
1004
+ //
1005
+ // // Get the average pixel value.
1006
+ // $average = floor(array_sum($pixels) / count($pixels));
1007
+ // // Each hash bit is set based on whether the current pixels value is above or below the average.
1008
+ // $hash = '';
1009
+ // foreach ($pixels as $pixel) {
1010
+ // if ($pixel > $average) {
1011
+ // $hash .= '1';
1012
+ // } else {
1013
+ // $hash .= '0';
1014
+ // }
1015
+ // }
1016
+ // return $hash;
1017
+ // }
1018
+
1019
+ /**
1020
+ * Resize helper function.
1021
+ *
1022
+ * @param int $newWidth
1023
+ * @param int $newHeight
1024
+ * @param int $targetX
1025
+ * @param int $targetY
1026
+ * @param int $srcX
1027
+ * @param int $srcY
1028
+ *
1029
+ * @throws \Exception
1030
+ */
1031
+ private function _resize( $newWidth, $newHeight, $targetX=0, $targetY=0, $srcX=0, $srcY=0 ){
1032
+
1033
+ $this->_imageCheck();
1034
+
1035
+ // Create blank image
1036
+ $newImage = Image::createBlank( $newWidth, $newHeight );
1037
+
1038
+ if( ImageType::PNG === $this->image->getType() ){
1039
+ // Preserve PNG transparency
1040
+ $newImage->fullAlphaMode( true );
1041
+ }
1042
+
1043
+ imagecopyresampled(
1044
+ $newImage->getCore(),
1045
+ $this->image->getCore(),
1046
+ $targetX,
1047
+ $targetY,
1048
+ $srcX,
1049
+ $srcY,
1050
+ $newWidth,
1051
+ $newHeight,
1052
+ $this->image->getWidth(),
1053
+ $this->image->getHeight()
1054
+ );
1055
+
1056
+ // Free memory of old resource
1057
+ imagedestroy( $this->image->getCore() );
1058
+
1059
+ // Resize image instance
1060
+ $this->image = new Image(
1061
+ $newImage->getCore(),
1062
+ $this->image->getImageFile(),
1063
+ $newWidth,
1064
+ $newHeight,
1065
+ $this->image->getType()
1066
+ );
1067
+
1068
+ }
1069
+
1070
+ /**
1071
+ * Convert alpha value of 0 - 1 to GD compatible alpha value of 0 - 127 where 0 is opaque and 127 is transparent
1072
+ *
1073
+ * @param float $alpha Alpha value of 0 - 1. Example: 0, 0.60, 0.9, 1
1074
+ *
1075
+ * @return int
1076
+ */
1077
+ public static function gdAlpha( $alpha ) {
1078
+
1079
+ $scale = round( 127 * $alpha );
1080
+
1081
+ return $invert = 127 - $scale;
1082
+ }
1083
+
1084
+ /**
1085
+ * Get image type base on file extension.
1086
+ *
1087
+ * @param int $imageFile File path to image.
1088
+ *
1089
+ * @return ImageType string Type of image.
1090
+ */
1091
+ private function _getImageTypeFromFileName( $imageFile ) {
1092
+ $ext = strtolower( (string) pathinfo( $imageFile, PATHINFO_EXTENSION ) );
1093
+
1094
+ if ( 'jpg' == $ext or 'jpeg' == $ext ) {
1095
+ return ImageType::JPEG;
1096
+ } else if ( 'gif' == $ext ) {
1097
+ return ImageType::GIF;
1098
+ } else if ( 'png' == $ext ) {
1099
+ return ImageType::PNG;
1100
+ } else {
1101
+ return ImageType::UNKNOWN;
1102
+ }
1103
+ }
1104
+
1105
+ /**
1106
+ * Check if editor has already been assigned an image.
1107
+ *
1108
+ * @throws \Exception
1109
+ */
1110
+ private function _imageCheck() {
1111
+ if ( null === $this->image ) {
1112
+ throw new \Exception( 'No image to edit.' );
1113
+ }
1114
+ }
1115
+ }
src/CycloneSlider/Grafika/Gd/Effect/Dither.php ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace CycloneSlider\Grafika\Gd\Effect;
4
+
5
+ use CycloneSlider\Grafika\EffectInterface;
6
+ use CycloneSlider\Grafika\Gd\Image;
7
+
8
+ /**
9
+ * Dither image using Floyd-Steinberg algorithm. Dithering will reduce the color to black and white and add noise.
10
+ */
11
+ class Dither implements EffectInterface{
12
+ /**
13
+ * Dither an image.
14
+ */
15
+ public function __construct()
16
+ {
17
+ }
18
+
19
+
20
+ /**
21
+ * @param Image $image
22
+ *
23
+ * @return Image
24
+ */
25
+ public function apply( $image ) {
26
+ return $this->floydSteinberg( $image );
27
+ }
28
+
29
+ /**
30
+ * @param Image $image
31
+ *
32
+ * @return Image
33
+ */
34
+ private function floydSteinberg( $image ){
35
+ $pixel = array();
36
+
37
+ // Localize vars
38
+ $width = $image->getWidth();
39
+ $height = $image->getHeight();
40
+ $gd = $image->getCore();
41
+
42
+ for ( $y = 0; $y < $height; $y+=1 ) {
43
+ for ( $x = 0; $x < $width; $x+=1 ) {
44
+
45
+ $color = imagecolorat( $gd, $x, $y );
46
+ $r = ($color >> 16) & 0xFF;
47
+ $g = ($color >> 8) & 0xFF;
48
+ $b = $color & 0xFF;
49
+
50
+ $gray = round($r * 0.3 + $g * 0.59 + $b * 0.11);
51
+
52
+ if(isset($pixel[$x][$y])){ // Add errors to color if there are
53
+ $gray += $pixel[$x][$y];
54
+ }
55
+
56
+ if ( $gray <= 127 ) { // Determine if black or white. Also has the benefit of clipping excess val due to adding the error
57
+ $blackOrWhite = 0;
58
+ } else {
59
+ $blackOrWhite = 255;
60
+ }
61
+
62
+ $oldPixel = $gray;
63
+ $newPixel = $blackOrWhite;
64
+
65
+ // Current pixel
66
+ imagesetpixel( $gd, $x, $y,
67
+ imagecolorallocate( $gd,
68
+ $newPixel,
69
+ $newPixel,
70
+ $newPixel
71
+ )
72
+ );
73
+
74
+ $qError = $oldPixel - $newPixel; // Quantization error
75
+
76
+ // Propagate error on neighbor pixels
77
+ if ( $x + 1 < $width ) {
78
+ $pixel[$x+1][$y] = (isset($pixel[$x+1][$y]) ? $pixel[$x+1][$y] : 0) + ($qError * (7 / 16));
79
+ }
80
+
81
+ if ( $x - 1 > 0 and $y + 1 < $height ) {
82
+ $pixel[$x-1][$y+1] = (isset($pixel[$x-1][$y+1]) ? $pixel[$x-1][$y+1] : 0) + ($qError * (3 / 16));
83
+ }
84
+
85
+ if ( $y + 1 < $height ) {
86
+ $pixel[$x][$y+1] = (isset($pixel[$x][$y+1]) ? $pixel[$x][$y+1] : 0) + ($qError * (5 / 16));
87
+ }
88
+
89
+ if ( $x + 1 < $width and $y + 1 < $height ) {
90
+ $pixel[$x+1][$y+1] = (isset($pixel[$x+1][$y+1]) ? $pixel[$x+1][$y+1] : 0) + ($qError * (1 / 16));
91
+ }
92
+
93
+ }
94
+ }
95
+ $type = $image->getType();
96
+ $file = $image->getImageFile();
97
+
98
+ return new Image( $gd, $file, $width, $height, $type ); // Create new image with updated core
99
+ }
100
+ }
src/CycloneSlider/Grafika/Gd/Image.php ADDED
@@ -0,0 +1,285 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace CycloneSlider\Grafika\Gd;
3
+
4
+ use CycloneSlider\Grafika\ImageType;
5
+ use CycloneSlider\Grafika\ImageInterface;
6
+
7
+ /**
8
+ * Immutable image class for GD.
9
+ * @package Grafika\Gd
10
+ */
11
+ final class Image implements ImageInterface {
12
+
13
+ /**
14
+ * @var resource GD resource ID.
15
+ */
16
+ private $gd;
17
+
18
+ /**
19
+ * @var string File path to image.
20
+ */
21
+ private $imageFile;
22
+
23
+ /**
24
+ * @var int Image width in pixels.
25
+ */
26
+ private $width;
27
+
28
+ /**
29
+ * @var int Image height in pixels.
30
+ */
31
+ private $height;
32
+
33
+ /**
34
+ * @var string Image type. See \Grafika\ImageType
35
+ */
36
+ private $type;
37
+
38
+ /**
39
+ * Image constructor.
40
+ *
41
+ * @param resource $gd Must use GD's imagecreate* family of functions to create a GD resource.
42
+ * @param string $imageFile
43
+ * @param int $width
44
+ * @param int $height
45
+ * @param string $type
46
+ */
47
+ public function __construct( $gd, $imageFile, $width, $height, $type ) {
48
+ $this->gd = $gd;
49
+ $this->imageFile = $imageFile;
50
+ $this->width = $width;
51
+ $this->height = $height;
52
+ $this->type = $type;
53
+ }
54
+
55
+ /**
56
+ * @param $imageFile
57
+ *
58
+ * @return Image
59
+ * @throws \Exception
60
+ */
61
+ public static function createFromFile( $imageFile ){
62
+ if ( ! file_exists( $imageFile ) ) {
63
+ throw new \Exception( sprintf('Could not open "%s". File does not exist.', $imageFile) );
64
+ }
65
+
66
+ $type = self::_guessType($imageFile);
67
+ if ( ImageType::GIF == $type) {
68
+
69
+ return self::createGif($imageFile);
70
+
71
+ } else if ( ImageType::JPEG == $type) {
72
+
73
+ return self::createJpeg($imageFile);
74
+
75
+ } else if ( ImageType::PNG == $type) {
76
+
77
+ return self::createPng($imageFile);
78
+
79
+ } else if ( ImageType::WBMP == $type) {
80
+
81
+ return self::createWbmp($imageFile);
82
+
83
+ } else {
84
+ throw new \Exception( sprintf('Could not open "%s". File type not supported.', $imageFile) );
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Load a JPEG image.
90
+ *
91
+ * @param string $imageFile File path to image.
92
+ *
93
+ * @return Image
94
+ * @throws \Exception
95
+ */
96
+ public static function createJpeg( $imageFile ){
97
+ $gd = @imagecreatefromjpeg( $imageFile );
98
+
99
+ if(!$gd){
100
+ throw new \Exception( sprintf('Could not open "%s". Not a valid %s file.', $imageFile, ImageType::JPEG ) );
101
+ }
102
+
103
+ return new self( $gd, $imageFile, imagesx( $gd ), imagesy( $gd ), ImageType::JPEG );
104
+ }
105
+
106
+ /**
107
+ * Load a PNG image.
108
+ *
109
+ * @param string $imageFile File path to image.
110
+ *
111
+ * @return Image
112
+ * @throws \Exception
113
+ */
114
+ public static function createPng( $imageFile ){
115
+ $gd = @imagecreatefrompng( $imageFile );
116
+
117
+ if(!$gd){
118
+ throw new \Exception( sprintf('Could not open "%s". Not a valid %s file.', $imageFile, ImageType::PNG) );
119
+ }
120
+
121
+ $image = new self( $gd, $imageFile, imagesx( $gd ), imagesy( $gd ), ImageType::PNG );
122
+ $image->fullAlphaMode( true );
123
+ return $image;
124
+ }
125
+
126
+
127
+ /**
128
+ * Load a GIF image.
129
+ *
130
+ * @param string $imageFile
131
+ *
132
+ * @return Image
133
+ * @throws \Exception
134
+ */
135
+ public static function createGif( $imageFile ){
136
+ $gd = @imagecreatefromgif( $imageFile );
137
+
138
+ if(!$gd){
139
+ throw new \Exception( sprintf('Could not open "%s". Not a valid %s file.', $imageFile, ImageType::GIF) );
140
+ }
141
+
142
+ return new self( $gd, $imageFile, imagesx( $gd ), imagesy( $gd ), ImageType::GIF );
143
+ }
144
+
145
+ /**
146
+ * Load a WBMP image.
147
+ *
148
+ * @param string $imageFile
149
+ *
150
+ * @return Image
151
+ * @throws \Exception
152
+ */
153
+ public static function createWbmp( $imageFile ){
154
+ $gd = @imagecreatefromwbmp( $imageFile );
155
+
156
+ if(!$gd){
157
+ throw new \Exception( sprintf('Could not open "%s". Not a valid %s file.', $imageFile, ImageType::WBMP) );
158
+ }
159
+
160
+ return new self( $gd, $imageFile, imagesx( $gd ), imagesy( $gd ), ImageType::WBMP );
161
+ }
162
+
163
+ /**
164
+ * Create a blank image.
165
+ *
166
+ * @param int $width Width in pixels.
167
+ * @param int $height Height in pixels.
168
+ *
169
+ * @return Image
170
+ */
171
+ public static function createBlank($width = 1, $height = 1){
172
+
173
+ return new self(imagecreatetruecolor($width, $height), '', $width, $height, ImageType::UNKNOWN);
174
+
175
+ }
176
+
177
+ /**
178
+ * Set the blending mode for an image. Allows transparent overlays on top of an image.
179
+ *
180
+ * @param bool $flag True to enable blending mode.
181
+ * @return self
182
+ */
183
+ public function alphaBlendingMode( $flag ){
184
+ imagealphablending( $this->gd, $flag );
185
+
186
+ return $this;
187
+ }
188
+
189
+ /**
190
+ * Enable/Disable transparency
191
+ *
192
+ * @param bool $flag True to enable alpha mode.
193
+ * @return self
194
+ */
195
+ public function fullAlphaMode( $flag ){
196
+ if( true === $flag ){
197
+ $this->alphaBlendingMode( false ); // Must be false for full alpha mode to work
198
+ }
199
+ imagesavealpha( $this->gd, $flag );
200
+
201
+ return $this;
202
+ }
203
+
204
+ /**
205
+ * Get GD resource ID.
206
+ *
207
+ * @return resource
208
+ */
209
+ public function getCore() {
210
+ return $this->gd;
211
+ }
212
+
213
+ /**
214
+ * Get image file path.
215
+ *
216
+ * @return string File path to image.
217
+ */
218
+ public function getImageFile() {
219
+ return $this->imageFile;
220
+ }
221
+
222
+ /**
223
+ * Get image width in pixels.
224
+ *
225
+ * @return int
226
+ */
227
+ public function getWidth() {
228
+ return $this->width;
229
+ }
230
+
231
+ /**
232
+ * Get image height in pixels.
233
+ *
234
+ * @return int
235
+ */
236
+ public function getHeight() {
237
+ return $this->height;
238
+ }
239
+
240
+ /**
241
+ * Get image type.
242
+ *
243
+ * @return string
244
+ */
245
+ public function getType() {
246
+ return $this->type;
247
+ }
248
+
249
+ /**
250
+ * @param $imageFile
251
+ *
252
+ * @return string
253
+ */
254
+ private static function _guessType( $imageFile ){
255
+ // Values from http://php.net/manual/en/image.constants.php starting with IMAGETYPE_GIF.
256
+ // 0 - unknown,
257
+ // 1 - GIF,
258
+ // 2 - JPEG,
259
+ // 3 - PNG
260
+ // 15 - WBMP
261
+ list($width, $height, $type) = getimagesize( $imageFile );
262
+
263
+ unset($width, $height);
264
+
265
+ if ( 1 == $type) {
266
+
267
+ return ImageType::GIF;
268
+
269
+ } else if ( 2 == $type) {
270
+
271
+ return ImageType::JPEG;
272
+
273
+ } else if ( 3 == $type) {
274
+
275
+ return ImageType::PNG;
276
+
277
+ } else if ( 15 == $type) {
278
+
279
+ return ImageType::WBMP;
280
+
281
+ }
282
+
283
+ return ImageType::UNKNOWN;
284
+ }
285
+ }
src/CycloneSlider/Grafika/Grafika.php ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace CycloneSlider\Grafika;
4
+
5
+ use CycloneSlider\Grafika\Gd\Editor as GdEditor;
6
+ use CycloneSlider\Grafika\Gd\Image as GdImage;
7
+ use CycloneSlider\Grafika\Imagick\Editor as ImagickEditor;
8
+ use CycloneSlider\Grafika\Imagick\Image as ImagickImage;
9
+
10
+ /**
11
+ * Contains factory methods for detecting editors, creating editors and images.
12
+ * @package Grafika
13
+ */
14
+ class Grafika
15
+ {
16
+
17
+ const DIR = __DIR__; // Grafika directory
18
+
19
+ public static function fontsDir()
20
+ {
21
+ $ds = DIRECTORY_SEPARATOR;
22
+ return realpath(self::DIR . $ds . '..' . $ds . '..') . $ds . 'fonts';
23
+ }
24
+
25
+ /**
26
+ * @param array $editorList Array of editor list names. Use this to change the order of evaluation for editors. Default order of evaluation is Imagick then GD.
27
+ *
28
+ * @return string Name of available editor.
29
+ * @throws \Exception Throws exception if there are no supported editors.
30
+ */
31
+ public static function detectAvailableEditor($editorList = array('Imagick', 'Gd'))
32
+ {
33
+
34
+ /* Get first supported editor instance. Order of editorList matter. */
35
+ foreach ($editorList as $editorName) {
36
+ if ('Imagick' === $editorName) {
37
+ $editorInstance = new ImagickEditor();
38
+ } else {
39
+ $editorInstance = new GdEditor();
40
+ }
41
+ /** @var EditorInterface $editorInstance */
42
+ if (true === $editorInstance->isAvailable()) {
43
+ return $editorName;
44
+ }
45
+ }
46
+
47
+ throw new \Exception('No supported editor.');
48
+ }
49
+
50
+ /**
51
+ * Creates the first available editor.
52
+ *
53
+ * @param array $editorList Array of editor list names. Use this to change the order of evaluation for editors. Default order of evaluation is Imagick then GD.
54
+ *
55
+ * @return EditorInterface
56
+ * @throws \Exception
57
+ */
58
+ public static function createEditor($editorList = array('Imagick', 'Gd'))
59
+ {
60
+ $editorName = self::detectAvailableEditor($editorList);
61
+ if ('Imagick' === $editorName) {
62
+ return new ImagickEditor();
63
+ } else {
64
+ return new GdEditor();
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Create an image.
70
+ * @param string $imageFile Path to image file.
71
+ *
72
+ * @return ImageInterface
73
+ * @throws \Exception
74
+ */
75
+ public static function createImage($imageFile)
76
+ {
77
+ $editorName = self::detectAvailableEditor();
78
+ if ('Imagick' === $editorName) {
79
+ return ImagickImage::createFromFile($imageFile);
80
+ } else {
81
+ return GdImage::createFromFile($imageFile);
82
+ }
83
+ }
84
+
85
+
86
+ /**
87
+ * Create a blank image.
88
+ *
89
+ * @param int $width Width of image in pixels.
90
+ * @param int $height Height of image in pixels.
91
+ *
92
+ * @return ImageInterface
93
+ * @throws \Exception
94
+ */
95
+ public static function createBlankImage($width = 1, $height = 1)
96
+ {
97
+ $editorName = self::detectAvailableEditor();
98
+ if ('Imagick' === $editorName) {
99
+ return ImagickImage::createBlank($width, $height);
100
+ } else {
101
+ return GdImage::createBlank($width, $height);
102
+ }
103
+ }
104
+
105
+ }
src/CycloneSlider/Grafika/ImageInterface.php ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace CycloneSlider\Grafika;
3
+
4
+ /**
5
+ * Interface ImageInterface
6
+ * @package Grafika
7
+ */
8
+ interface ImageInterface {
9
+
10
+ /**
11
+ * @param $imageFile
12
+ *
13
+ * @return mixed
14
+ */
15
+ public static function createFromFile( $imageFile );
16
+
17
+ /**
18
+ * @param int $width
19
+ * @param int $height
20
+ *
21
+ * @return mixed
22
+ */
23
+ public static function createBlank($width = 1, $height = 1);
24
+
25
+ /**
26
+ * @return mixed
27
+ */
28
+ public function getCore();
29
+
30
+ /**
31
+ * @return mixed
32
+ */
33
+ public function getImageFile();
34
+
35
+ /**
36
+ * @return mixed
37
+ */
38
+ public function getWidth();
39
+
40
+ /**
41
+ * @return mixed
42
+ */
43
+ public function getHeight();
44
+
45
+ /**
46
+ * @return mixed
47
+ */
48
+ public function getType();
49
+ }
src/CycloneSlider/Grafika/ImageType.php ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace CycloneSlider\Grafika;
4
+
5
+ /**
6
+ * Class ImageType. Represent the different image types for GD and Imagick consistently.
7
+ *
8
+ * @package Grafika
9
+ */
10
+ class ImageType {
11
+
12
+ const UNKNOWN = '';
13
+
14
+ const GIF = 'GIF';
15
+
16
+ const JPEG = 'JPEG';
17
+
18
+ const PNG = 'PNG';
19
+
20
+ const WBMP = 'WBMP';
21
+ }
src/CycloneSlider/Grafika/Imagick/DrawingObject/CubicBezier.php ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace CycloneSlider\Grafika\Imagick\DrawingObject;
3
+
4
+ use CycloneSlider\Grafika\DrawingObject\CubicBezier as Base;
5
+ use CycloneSlider\Grafika\DrawingObjectInterface;
6
+ use CycloneSlider\Grafika\Imagick\Image;
7
+ use CycloneSlider\Grafika\ImageInterface;
8
+
9
+ /**
10
+ * Class CubicBezier
11
+ * @package Grafika
12
+ */
13
+ class CubicBezier extends Base implements DrawingObjectInterface
14
+ {
15
+
16
+ /**
17
+ * @param ImageInterface $image
18
+ * @return Image
19
+ */
20
+ public function draw($image)
21
+ {
22
+ // Localize vars
23
+ $width = $image->getWidth();
24
+ $height = $image->getHeight();
25
+ $imagick = $image->getCore();
26
+
27
+ $draw = new \ImagickDraw();
28
+
29
+ $strokeColor = new \ImagickPixel($this->getColor()->getHexString());
30
+ $fillColor = new \ImagickPixel('rgba(0,0,0,0)');
31
+
32
+ $draw->setStrokeOpacity(1);
33
+ $draw->setStrokeColor($strokeColor);
34
+ $draw->setFillColor($fillColor);
35
+
36
+ $points = array(
37
+ array('x'=> $this->point1[0], 'y'=> $this->point1[1]),
38
+ array('x'=> $this->control1[0], 'y'=> $this->control1[1]),
39
+ array('x'=> $this->control2[0], 'y'=> $this->control2[1]),
40
+ array('x'=> $this->point2[0], 'y'=> $this->point2[1]),
41
+ );
42
+ $draw->bezier($points);
43
+
44
+ // Render the draw commands in the ImagickDraw object
45
+ $imagick->drawImage($draw);
46
+
47
+ $type = $image->getType();
48
+ $file = $image->getImageFile();
49
+ return new Image($imagick, $file, $width, $height, $type); // Create new image with updated core
50
+ }
51
+ }
src/CycloneSlider/Grafika/Imagick/DrawingObject/Ellipse.php ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace CycloneSlider\Grafika\Imagick\DrawingObject;
3
+
4
+ use CycloneSlider\Grafika\DrawingObject\Ellipse as Base;
5
+ use CycloneSlider\Grafika\DrawingObjectInterface;
6
+ use CycloneSlider\Grafika\ImageInterface;
7
+
8
+ /**
9
+ * Class Ellipse
10
+ * @package Grafika
11
+ */
12
+ class Ellipse extends Base implements DrawingObjectInterface
13
+ {
14
+
15
+ /**
16
+ * @param ImageInterface $image
17
+ * @return ImageInterface
18
+ */
19
+ public function draw($image)
20
+ {
21
+
22
+ $strokeColor = new \ImagickPixel($this->getBorderColor()->getHexString());
23
+ $fillColor = new \ImagickPixel($this->getFillColor()->getHexString());
24
+
25
+ $draw = new \ImagickDraw();
26
+ $draw->setStrokeColor($strokeColor);
27
+ $draw->setFillColor($fillColor);
28
+
29
+ $draw->setStrokeWidth($this->borderSize);
30
+
31
+ list($x, $y) = $this->pos;
32
+ $left = $x + $this->width / 2;
33
+ $top = $y + $this->height / 2;
34
+ $draw->ellipse($left, $top, $this->width/2, $this->height/2, 0, 360);
35
+
36
+ $image->getCore()->drawImage($draw);
37
+
38
+ return $image;
39
+ }
40
+ }
src/CycloneSlider/Grafika/Imagick/DrawingObject/Line.php ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace CycloneSlider\Grafika\Imagick\DrawingObject;
3
+
4
+ use CycloneSlider\Grafika\DrawingObject\Line as Base;
5
+ use CycloneSlider\Grafika\DrawingObjectInterface;
6
+ use CycloneSlider\Grafika\Imagick\Image;
7
+
8
+ /**
9
+ * Class Line
10
+ * @package Grafika
11
+ */
12
+ class Line extends Base implements DrawingObjectInterface
13
+ {
14
+
15
+ /**
16
+ * @param Image $image
17
+ *
18
+ * @return Image
19
+ */
20
+ public function draw($image)
21
+ {
22
+
23
+ $strokeColor = new \ImagickPixel($this->getColor()->getHexString());
24
+
25
+ $draw = new \ImagickDraw();
26
+
27
+ $draw->setStrokeColor($strokeColor);
28
+
29
+ $draw->setStrokeWidth($this->thickness);
30
+
31
+ list($x1, $y1) = $this->point1;
32
+ list($x2, $y2) = $this->point2;
33
+ $draw->line($x1, $y1, $x2, $y2);
34
+
35
+ $image->getCore()->drawImage($draw);
36
+
37
+ return $image;
38
+ }
39
+
40
+
41
+ }
src/CycloneSlider/Grafika/Imagick/DrawingObject/Polygon.php ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace CycloneSlider\Grafika\Imagick\DrawingObject;
3
+
4
+ use CycloneSlider\Grafika\DrawingObject\Polygon as Base;
5
+ use CycloneSlider\Grafika\DrawingObjectInterface;
6
+
7
+ /**
8
+ * Class Polygon
9
+ * @package Grafika
10
+ */
11
+ class Polygon extends Base implements DrawingObjectInterface{
12
+
13
+ public function draw( $image ) {
14
+ $strokeColor = new \ImagickPixel($this->getBorderColor()->getHexString());
15
+ if( null !== $this->fillColor) {
16
+ $fillColor = new \ImagickPixel($this->getFillColor()->getHexString());
17
+ } else {
18
+ $fillColor = new \ImagickPixel('rgba(0,0,0,0)');
19
+ }
20
+
21
+ $draw = new \ImagickDraw();
22
+ $draw->setStrokeOpacity(1);
23
+ $draw->setStrokeColor($strokeColor);
24
+ $draw->setFillColor($fillColor);
25
+ $draw->setStrokeWidth($this->borderSize);
26
+
27
+ $draw->polygon($this->points());
28
+
29
+ $image->getCore()->drawImage($draw);
30
+
31
+ return $image;
32
+ }
33
+
34
+ protected function points(){
35
+ $points = array();
36
+ foreach($this->points as $i=>$pos){
37
+ $points[$i] = array(
38
+ 'x' => $pos[0],
39
+ 'y' => $pos[1]
40
+ );
41
+ }
42
+ if( count($points) < 3 ){
43
+ throw new \Exception('Polygon needs at least 3 points.');
44
+ }
45
+ return $points;
46
+ }
47
+
48
+ }
src/CycloneSlider/Grafika/Imagick/DrawingObject/QuadraticBezier.php ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace CycloneSlider\Grafika\Imagick\DrawingObject;
3
+
4
+ use CycloneSlider\Grafika\DrawingObject\QuadraticBezier as Base;
5
+ use CycloneSlider\Grafika\DrawingObjectInterface;
6
+ use CycloneSlider\Grafika\Imagick\Image;
7
+ use CycloneSlider\Grafika\ImageInterface;
8
+
9
+ /**
10
+ * Class QuadraticBezier
11
+ * @package Grafika
12
+ */
13
+ class QuadraticBezier extends Base implements DrawingObjectInterface
14
+ {
15
+ /**
16
+ * @param ImageInterface $image
17
+ * @return Image
18
+ */
19
+ public function draw($image)
20
+ {
21
+ // Localize vars
22
+ $width = $image->getWidth();
23
+ $height = $image->getHeight();
24
+ $imagick = $image->getCore();
25
+
26
+ $draw = new \ImagickDraw();
27
+
28
+ $strokeColor = new \ImagickPixel($this->getColor()->getHexString());
29
+ $fillColor = new \ImagickPixel('rgba(0,0,0,0)');
30
+
31
+ $draw->setStrokeOpacity(1);
32
+ $draw->setStrokeColor($strokeColor);
33
+ $draw->setFillColor($fillColor);
34
+
35
+
36
+
37
+ list($x1, $y1) = $this->point1;
38
+ list($x2, $y2) = $this->control;
39
+ list($x3, $y3) = $this->point2;
40
+ $draw->pathStart();
41
+ $draw->pathMoveToAbsolute($x1, $y1);
42
+ $draw->pathCurveToQuadraticBezierAbsolute(
43
+ $x2, $y2,
44
+ $x3, $y3
45
+ );
46
+ $draw->pathFinish();
47
+
48
+ // Render the draw commands in the ImagickDraw object
49
+ $imagick->drawImage($draw);
50
+
51
+ $type = $image->getType();
52
+ $file = $image->getImageFile();
53
+ return new Image($imagick, $file, $width, $height, $type); // Create new image with updated core
54
+ }
55
+ }
src/CycloneSlider/Grafika/Imagick/DrawingObject/Rectangle.php ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace CycloneSlider\Grafika\Imagick\DrawingObject;
3
+
4
+ use CycloneSlider\Grafika\DrawingObject\Rectangle as Base;
5
+ use CycloneSlider\Grafika\DrawingObjectInterface;
6
+
7
+ /**
8
+ * Class Rectangle
9
+ * @package Grafika
10
+ */
11
+ class Rectangle extends Base implements DrawingObjectInterface{
12
+
13
+ // TODO: check use of stroke property instead of using two rectangles
14
+ public function draw( $image ) {
15
+ $strokeColor = new \ImagickPixel( $this->getBorderColor()->getHexString() );
16
+ $fillColor = new \ImagickPixel( $this->getFillColor()->getHexString() );
17
+
18
+ $draw = new \ImagickDraw();
19
+ $draw->setFillColor($strokeColor);
20
+ $draw->setStrokeOpacity( 0 );
21
+
22
+ $x1 = $this->pos[0];
23
+ $x2 = $x1 + $this->getWidth();
24
+ $y1 = $this->pos[1];
25
+ $y2 = $y1 + $this->getHeight();
26
+
27
+ $draw->rectangle( $x1, $y1, $x2, $y2 );
28
+
29
+ $borderSize = $this->getBorderSize();
30
+ $innerRect = new \ImagickDraw();
31
+ $innerRect->setStrokeOpacity( 0 );
32
+ $innerRect->setFillColor($fillColor);
33
+ $innerRect->rectangle( $x1 + $borderSize, $y1 + $borderSize, $x2 - $borderSize, $y2 - $borderSize );
34
+
35
+ $image->getCore()->drawImage($draw);
36
+ $image->getCore()->drawImage($innerRect);
37
+
38
+ return $image;
39
+ }
40
+
41
+ }
src/CycloneSlider/Grafika/Imagick/Editor.php ADDED
@@ -0,0 +1,975 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace CycloneSlider\Grafika\Imagick;
4
+
5
+ use CycloneSlider\Grafika\DrawingObjectInterface;
6
+ use CycloneSlider\Grafika\EditorInterface;
7
+ use CycloneSlider\Grafika\EffectInterface;
8
+ use CycloneSlider\Grafika\Grafika;
9
+ use CycloneSlider\Grafika\ImageInterface;
10
+ use CycloneSlider\Grafika\ImageType;
11
+ use CycloneSlider\Grafika\Color;
12
+ use CycloneSlider\Grafika\Imagick\DrawingObject\CubicBezier;
13
+ use CycloneSlider\Grafika\Imagick\DrawingObject\Ellipse;
14
+ use CycloneSlider\Grafika\Imagick\DrawingObject\Line;
15
+ use CycloneSlider\Grafika\Imagick\DrawingObject\Polygon;
16
+ use CycloneSlider\Grafika\Imagick\DrawingObject\QuadraticBezier;
17
+ use CycloneSlider\Grafika\Imagick\DrawingObject\Rectangle;
18
+ use CycloneSlider\Grafika\Imagick\Effect\Dither;
19
+
20
+ /**
21
+ * Imagick Editor class. Uses the PHP Imagick library.
22
+ * @package Grafika\Imagick
23
+ */
24
+ final class Editor implements EditorInterface {
25
+
26
+ /**
27
+ * @var Image Holds the image instance
28
+ */
29
+ private $image;
30
+
31
+ /**
32
+ * Constructor.
33
+ */
34
+ function __construct() {
35
+ $this->image = null;
36
+ }
37
+
38
+ /**
39
+ * @param EffectInterface $effect
40
+ *
41
+ * @return $this
42
+ */
43
+ public function apply( $effect ){
44
+ $this->image = $effect->apply( $this->image );
45
+ return $this;
46
+ }
47
+
48
+ /**
49
+ * Creates a cubic bezier. Cubic bezier has 2 control points.
50
+ * @param array $point1 Array of X and Y value for start point.
51
+ * @param array $control1 Array of X and Y value for control point 1.
52
+ * @param array $control2 Array of X and Y value for control point 2.
53
+ * @param array $point2 Array of X and Y value for end point.
54
+ * @param Color|string $color Color of the curve. Accepts hex string or a Color object. Defaults to black.
55
+ * @return Editor
56
+ */
57
+ public function bezierCubic($point1, $control1, $control2, $point2, $color = '#000000') {
58
+ if(is_string($color)){
59
+ $color = new Color($color);
60
+ }
61
+ $obj = new CubicBezier($point1, $control1, $control2, $point2, $color);
62
+ return $this->draw($obj);
63
+ }
64
+
65
+ /**
66
+ * Creates a quadratic bezier. Quadratic bezier has 1 control point.
67
+ * @param array $point1 Array of X and Y value for start point.
68
+ * @param array $control Array of X and Y value for control point.
69
+ * @param array $point2 Array of X and Y value for end point.
70
+ * @param Color|string $color Color of the curve. Accepts hex string or a Color object. Defaults to black.
71
+ * @return Editor
72
+ */
73
+ public function bezierQuad($point1, $control, $point2, $color = '#000000') {
74
+ if(is_string($color)){
75
+ $color = new Color($color);
76
+ }
77
+ $obj = new QuadraticBezier($point1, $control, $point2, $color);
78
+ return $this->draw($obj);
79
+ }
80
+
81
+ /**
82
+ * Create a blank image given width and height.
83
+ *
84
+ * @param int $width Width of image in pixels.
85
+ * @param int $height Height of image in pixels.
86
+ *
87
+ * @return self
88
+ */
89
+ public function blank( $width, $height ) {
90
+ $this->image = Image::createBlank( $width, $height );
91
+
92
+ return $this;
93
+ }
94
+
95
+ /**
96
+ * Compare two images and returns a hamming distance. A value of 0 indicates a likely similar picture. A value between 1 and 10 is potentially a variation. A value greater than 10 is likely a different image.
97
+ * @param ImageInterface|string $image1
98
+ * @param ImageInterface|string $image2
99
+ * @return int Hamming distance. Note: This breaks the chain if you are doing fluent api calls as it does not return an Editor.
100
+ * @throws \Exception
101
+ */
102
+ public function compare( $image1, $image2 ){
103
+
104
+ if ( is_string( $image1 ) ) { // If string passed, turn it into a Image object
105
+ $image1 = Image::createFromFile( $image1 );
106
+ }
107
+
108
+ if ( is_string( $image2 ) ) { // If string passed, turn it into a Image object
109
+ $image2 = Image::createFromFile( $image2 );
110
+ }
111
+
112
+ $bin1 = $this->_differenceHash($image1);
113
+ $bin2 = $this->_differenceHash($image2);
114
+ $str1 = str_split($bin1);
115
+ $str2 = str_split($bin2);
116
+ $distance = 0;
117
+ foreach($str1 as $i=>$char){
118
+ if($char !== $str2[$i]){
119
+ $distance++;
120
+ }
121
+ }
122
+ return $distance;
123
+
124
+ }
125
+
126
+ /**
127
+ * Crop the image to the given dimension and position.
128
+ *
129
+ * @param int $cropWidth Crop width in pixels.
130
+ * @param int $cropHeight Crop Height in pixels.
131
+ * @param int|string $cropX The number of pixels from the left of the image. Can also be the words "left", "center", and "right".
132
+ * @param int|string $cropY The number of pixels from the right of the image. Can also be the words "top", "center", and "bottom".
133
+ *
134
+ * @return self
135
+ */
136
+ public function crop($cropWidth, $cropHeight, $cropX='center', $cropY='center') {
137
+
138
+ if(is_string($cropX)){
139
+ // Compute position from string
140
+ switch ($cropX){
141
+ case 'left':
142
+ $x = 0;
143
+ break;
144
+
145
+ case 'right':
146
+ $x = $this->image->getWidth() - $cropWidth;
147
+ break;
148
+
149
+ case 'center':
150
+ default:
151
+ $x = (int) round( ($this->image->getWidth()/2) - ($cropWidth/2) );
152
+ break;
153
+ }
154
+ } else {
155
+ $x = $cropX;
156
+ }
157
+
158
+ if(is_string($cropY)){
159
+ switch ($cropY){
160
+ case 'top':
161
+ $y = 0;
162
+ break;
163
+
164
+ case 'bottom':
165
+ $y = $this->image->getHeight() - $cropHeight;
166
+ break;
167
+
168
+ case 'center':
169
+ default:
170
+ $y = (int) round( ($this->image->getHeight()/2) - ($cropHeight/2) );
171
+ break;
172
+ }
173
+ } else {
174
+ $y = $cropY;
175
+ }
176
+
177
+ $this->image->getCore()->cropImage( $cropWidth, $cropHeight, $x, $y );
178
+
179
+ return $this;
180
+ }
181
+
182
+ /**
183
+ * Dither image using Floyd-Steinberg algorithm. Dithering will reduce the color to black and white and add noise.
184
+ * @return EditorInterface An instance of image editor.
185
+ */
186
+ public function dither(){
187
+ $e = new Dither();
188
+ return $this->apply($e);
189
+ }
190
+
191
+ /**
192
+ * @param DrawingObjectInterface $drawingObject
193
+ *
194
+ * @return $this
195
+ */
196
+ public function draw( $drawingObject ){
197
+ $this->image = $drawingObject->draw( $this->image );
198
+ return $this;
199
+ }
200
+
201
+ /**
202
+ * Creates an ellipse.
203
+ *
204
+ * @param int $width Width of ellipse in pixels.
205
+ * @param int $height Height of ellipse in pixels.
206
+ * @param array $pos Array containing int X and int Y position of the ellipse from top left of the canvass.
207
+ * @param int $borderSize Size of the border in pixels. Defaults to 1 pixel. Set to 0 for no border.
208
+ * @param Color|string|null $borderColor Border color. Defaults to black. Set to null for no color.
209
+ * @param Color|string|null $fillColor Fill color. Defaults to white. Set to null for no color.
210
+ *
211
+ * @return EditorInterface An instance of image editor.
212
+ */
213
+ public function ellipse($width, $height, array $pos, $borderSize = 1, $borderColor = '#000000', $fillColor = '#FFFFFF') {
214
+ if(is_string($borderColor)){
215
+ $borderColor = new Color($borderColor);
216
+ }
217
+ if(is_string($fillColor)){
218
+ $fillColor = new Color($fillColor);
219
+ }
220
+ $obj = new Ellipse($width, $height, $pos, $borderSize, $borderColor, $fillColor);
221
+ return $this->draw($obj);
222
+ }
223
+
224
+ /**
225
+ * Compare if two images are equal. It will compare if the two images are of the same width and height. If the dimensions differ, it will return false. If the dimensions are equal, it will loop through each pixels. If one of the pixel don't match, it will return false. The pixels are compared using their RGB (Red, Green, Blue) values.
226
+ *
227
+ * @param string|ImageInterface $image1 Can be an instance of Image or string containing the file system path to image.
228
+ * @param string|ImageInterface $image2 Can be an instance of Image or string containing the file system path to image.
229
+ *
230
+ * @return bool True if equals false if not. Note: This breaks the chain if you are doing fluent api calls as it does not return an Editor.
231
+ * @throws \Exception
232
+ */
233
+ public function equal( $image1, $image2 ){
234
+
235
+ if ( is_string( $image1 ) ) { // If string passed, turn it into a Image object
236
+ $image1 = Image::createFromFile( $image1 );
237
+ }
238
+
239
+ if ( is_string( $image2 ) ) { // If string passed, turn it into a Image object
240
+ $image2 = Image::createFromFile( $image2 );
241
+ }
242
+
243
+ // Check if image dimensions are equal
244
+ if($image1->getWidth() !== $image2->getWidth() or $image1->getHeight() !== $image2->getHeight()) {
245
+
246
+ return false;
247
+
248
+ } else {
249
+
250
+ // Loop using image1
251
+ $pixelIterator = $image1->getCore()->getPixelIterator();
252
+ foreach ($pixelIterator as $row => $pixels) { /* Loop through pixel rows */
253
+ foreach ($pixels as $column => $pixel) { /* Loop through the pixels in the row (columns) */
254
+ /**
255
+ * Get image1 pixel
256
+ * @var $pixel \ImagickPixel */
257
+ $rgba1 = $pixel->getColor();
258
+
259
+ // Get image2 pixel
260
+ $rgba2 = $image2->getCore()->getImagePixelColor( $column, $row )->getColor();
261
+
262
+ // Compare pixel value
263
+ if (
264
+ $rgba1['r'] !== $rgba2['r'] or
265
+ $rgba1['g'] !== $rgba2['g'] or
266
+ $rgba1['b'] !== $rgba2['b']
267
+ ) {
268
+ return false;
269
+ }
270
+ }
271
+ $pixelIterator->syncIterator(); /* Sync the iterator, this is important to do on each iteration */
272
+ }
273
+ }
274
+ return true;
275
+ }
276
+
277
+ /**
278
+ * Fill entire image with color.
279
+ *
280
+ * @param Color $color Color object
281
+ * @param int $x X-coordinate of start point
282
+ * @param int $y Y-coordinate of start point
283
+ *
284
+ * @return self
285
+ */
286
+ public function fill( $color, $x = 0, $y = 0 ){
287
+
288
+ $this->_imageCheck();
289
+
290
+ $target = $this->image->getCore()->getImagePixelColor($x, $y);
291
+ $this->image->getCore()->floodfillPaintImage( $color->getHexString(), 1, $target, $x, $y, false);
292
+
293
+ return $this;
294
+ }
295
+
296
+ /**
297
+ * Free the current image clearing resources associated with it.
298
+ */
299
+ public function free(){
300
+ if ( null !== $this->image ) {
301
+ if( null !== $this->image->getCore() ) {
302
+ $this->image->getCore()->clear();
303
+ }
304
+ } else {
305
+ $this->image = null;
306
+ }
307
+ }
308
+
309
+ /**
310
+ * Converts image to grayscale.
311
+ *
312
+ * @return EditorInterface An instance of image editor.
313
+ */
314
+ public function grayscale(){
315
+
316
+ $this->_imageCheck();
317
+
318
+ $this->image->getCore()->modulateImage(100, 0, 100);
319
+
320
+ return $this;
321
+ }
322
+
323
+ /**
324
+ * Alias for grayscale.
325
+ *
326
+ * @return EditorInterface An instance of image editor.
327
+ */
328
+ public function greyscale(){
329
+ return $this->grayscale();
330
+ }
331
+
332
+ /**
333
+ * @return Image
334
+ */
335
+ public function getImage() {
336
+ return $this->image;
337
+ }
338
+
339
+ /**
340
+ * Checks if the editor is available on the current PHP install.
341
+ *
342
+ * @return bool
343
+ */
344
+ public function isAvailable(){
345
+ // First, test Imagick's extension and classes.
346
+ if ( false === extension_loaded( 'imagick' ) ||
347
+ false === class_exists( 'Imagick' ) ||
348
+ false === class_exists( 'ImagickDraw' ) ||
349
+ false === class_exists( 'ImagickPixel' ) ||
350
+ false === class_exists( 'ImagickPixelIterator' )
351
+ ){
352
+ return false;
353
+ }
354
+
355
+ return true;
356
+ }
357
+
358
+ /**
359
+ * Creates a line.
360
+ *
361
+ * @param array $point1 Array containing int X and int Y position of the starting point.
362
+ * @param array $point2 Array containing int X and int Y position of the starting point.
363
+ * @param int $thickness Thickness in pixel. Note: This is currently ignored in GD editor and falls back to 1.
364
+ * @param Color|string $color Color of the line. Defaults to black.
365
+ * @return Editor
366
+ */
367
+ public function line(array $point1, array $point2, $thickness = 1, $color = '#000000') {
368
+ if(is_string($color)){
369
+ $color = new Color($color);
370
+ }
371
+ $obj = new Line($point1, $point2, $thickness, $color);
372
+ return $this->draw($obj);
373
+ }
374
+
375
+ /**
376
+ * Sets the image to the specified opacity level where 1.0 is fully opaque and 0.0 is fully transparent.
377
+ *
378
+ * @param float $opacity
379
+ *
380
+ * @return self
381
+ * @throws \Exception
382
+ */
383
+ public function opacity( $opacity ){
384
+
385
+ $this->_imageCheck();
386
+
387
+ // Bounds checks
388
+ $opacity = ($opacity > 1) ? 1 : $opacity;
389
+ $opacity = ($opacity < 0) ? 0 : $opacity;
390
+
391
+ $this->image->getCore()->setImageOpacity( $opacity );
392
+
393
+ return $this;
394
+ }
395
+
396
+ /**
397
+ * Opens an image file for manipulation specified by $target.
398
+ *
399
+ * @param mixed $target Can be an instance of Image or a string containing file system path to the image.
400
+ *
401
+ * @return Editor
402
+ * @throws \Exception
403
+ */
404
+ public function open( $target ) {
405
+ if( $target instanceof ImageInterface) {
406
+ $this->openImage( $target );
407
+ } else if (is_string($target)){
408
+ $this->openFile( $target );
409
+ } else {
410
+ throw new \Exception( 'Could not open image.' );
411
+ }
412
+
413
+ return $this;
414
+ }
415
+
416
+ /**
417
+ * Open an image by passing an instance of Image.
418
+ *
419
+ * @param ImageInterface $image
420
+ *
421
+ * @return $this
422
+ */
423
+ public function openImage( $image ){
424
+ $this->image = $image;
425
+
426
+ return $this;
427
+ }
428
+
429
+ /**
430
+ * Open an image by passing a file system path.
431
+ *
432
+ * @param string $file A full path to the image in the file system.
433
+ *
434
+ * @return $this
435
+ * @throws \Exception
436
+ */
437
+ public function openFile( $file ){
438
+ $this->image = Image::createFromFile( $file );
439
+
440
+ return $this;
441
+ }
442
+
443
+ /**
444
+ * Overlay an image on top of the current image.
445
+ *
446
+ * @param Image|string $overlay Can be a string containing a file path of the image to overlay or an Image object.
447
+ * @param string|int $xPos Horizontal position of image. Can be 'left','center','right' or integer number. Defaults to 'center'.
448
+ * @param string|int $yPos Vertical position of image. Can be 'top', 'center','bottom' or integer number. Defaults to 'center'.
449
+ * @param null $width
450
+ * @param null $height
451
+ *
452
+ * @return Editor
453
+ * @throws \Exception
454
+ */
455
+ public function overlay( $overlay, $xPos = 'center', $yPos = 'center', $width = null, $height = null ) {
456
+
457
+ $this->_imageCheck();
458
+
459
+ if ( is_string( $overlay ) ) { // If string passed, turn it into a Image object
460
+ $overlay = Image::createFromFile( $overlay );
461
+ }
462
+
463
+ // Resize overlay
464
+ if($width and $height){
465
+
466
+ $overlayWidth = $overlay->getWidth();
467
+ $overlayHeight = $overlay->getHeight();
468
+
469
+ if(is_numeric($width)){
470
+ $overlayWidth = (int) $width;
471
+ } else {
472
+ $percent = strpos( $width, '%' );
473
+ if( false !== $percent){
474
+ $overlayWidth = intval( $width ) / 100 * $this->image->getWidth();
475
+ }
476
+ }
477
+
478
+ if(is_numeric($height)){
479
+ $overlayHeight = (int) $height;
480
+ } else {
481
+ $percent = strpos( $height, '%' );
482
+ if( false !== $percent){
483
+ $overlayHeight = intval( $height ) / 100 * $this->image->getHeight();
484
+ }
485
+ }
486
+
487
+ $editor = new Editor();
488
+ $editor->setImage($overlay);
489
+ $editor->resizeFit( $overlayWidth, $overlayHeight );
490
+ $overlay = $editor->getImage();
491
+ }
492
+
493
+ //$x = $y = 0;
494
+
495
+ if ( is_string( $xPos ) ) {
496
+ // Compute position from string
497
+ switch ( $xPos ) {
498
+ case 'left':
499
+ $x = 0;
500
+ break;
501
+
502
+ case 'right':
503
+ $x = $this->image->getWidth() - $overlay->getWidth();
504
+ break;
505
+
506
+ case 'center':
507
+ default:
508
+ $x = (int) round( ( $this->image->getWidth() / 2 ) - ( $overlay->getWidth() / 2 ) );
509
+ break;
510
+ }
511
+ } else {
512
+ $x = $xPos;
513
+ }
514
+
515
+ if ( is_string( $yPos ) ) {
516
+ switch ( $yPos ) {
517
+ case 'top':
518
+ $y = 0;
519
+ break;
520
+
521
+ case 'bottom':
522
+ $y = $this->image->getHeight() - $overlay->getHeight();
523
+ break;
524
+
525
+ case 'center':
526
+ default:
527
+ $y = (int) round( ( $this->image->getHeight() / 2 ) - ( $overlay->getHeight() / 2 ) );
528
+ break;
529
+ }
530
+ } else {
531
+ $y = $yPos;
532
+ }
533
+
534
+ // Overlay the image on the original image
535
+ $this->image->getCore()->compositeImage($overlay->getCore(), \Imagick::COMPOSITE_OVER, $x, $y);
536
+
537
+ return $this;
538
+
539
+ }
540
+
541
+ /**
542
+ * Creates a polygon.
543
+ *
544
+ * @param array $points Array of all X and Y positions. Must have at least three positions.
545
+ * @param int $borderSize Size of the border in pixels. Defaults to 1 pixel. Set to 0 for no border.
546
+ * @param Color|string|null $borderColor Border color. Defaults to black. Set to null for no color.
547
+ * @param Color|string|null $fillColor Fill color. Defaults to white. Set to null for no color.
548
+ *
549
+ * @return EditorInterface An instance of image editor.
550
+ */
551
+ public function polygon($points, $borderSize = 1, $borderColor = '#000000', $fillColor = '#FFFFFF'){
552
+ if(is_string($borderColor)){
553
+ $borderColor = new Color($borderColor);
554
+ }
555
+ if(is_string($fillColor)){
556
+ $fillColor = new Color($fillColor);
557
+ }
558
+ $obj = new Polygon($points, $borderSize, $borderColor, $fillColor);
559
+ return $this->draw($obj);
560
+ }
561
+
562
+ /**
563
+ * Creates a rectangle.
564
+ * @param int $width Width of rectangle in pixels.
565
+ * @param int $height Height in pixels.
566
+ * @param array $pos Array of X and Y position. X is the distance in pixels from the left of the canvass to the left of the rectangle. Y is the distance from the top of the canvass to the top of the rectangle. Defaults to array(0,0).
567
+ * @param int $borderSize Size of the border in pixels. Defaults to 1 pixel. Set to 0 for no border.
568
+ * @param Color|string|null $borderColor Border color. Defaults to black. Set to null for no color.
569
+ * @param Color|string|null $fillColor Fill color. Defaults to white. Set to null for no color.
570
+ * @return Editor
571
+ */
572
+ public function rectangle($width, $height, $pos = array(0, 0), $borderSize = 1, $borderColor = '#000000', $fillColor = '#FFFFFF') {
573
+ if(is_string($borderColor)){
574
+ $borderColor = new Color($borderColor);
575
+ }
576
+ if(is_string($fillColor)){
577
+ $fillColor = new Color($fillColor);
578
+ }
579
+ $obj = new Rectangle($width, $height, $pos, $borderSize, $borderColor, $fillColor);
580
+ return $this->draw($obj);
581
+ }
582
+
583
+ /**
584
+ * Wrapper function for the resizeXXX family of functions. Resize image given width, height and mode.
585
+ *
586
+ * @param int $newWidth Width in pixels.
587
+ * @param int $newHeight Height in pixels.
588
+ * @param string $mode Resize mode. Possible values: "exact", "exactHeight", "exactWidth", "fill", "fit".
589
+ *
590
+ * @return self
591
+ */
592
+ public function resize( $newWidth, $newHeight, $mode='fit' ){
593
+
594
+ switch ($mode){
595
+ case 'exact':
596
+ $this->resizeExact( $newWidth, $newHeight );
597
+ break;
598
+ case 'fill':
599
+ $this->resizeFill($newWidth, $newHeight);
600
+ break;
601
+ case 'exactWidth':
602
+ $this->resizeExactWidth( $newWidth );
603
+ break;
604
+ case 'exactHeight':
605
+ $this->resizeExactHeight( $newHeight );
606
+ break;
607
+ case 'fit':
608
+ $this->resizeFit($newWidth, $newHeight);
609
+ break;
610
+ }
611
+
612
+ return $this;
613
+ }
614
+
615
+ /**
616
+ * Resize image to exact dimensions ignoring aspect ratio. Useful if you want to force exact width and height.
617
+ *
618
+ * @param int $newWidth Width in pixels.
619
+ * @param int $newHeight Height in pixels.
620
+ *
621
+ * @return self
622
+ */
623
+ public function resizeExact( $newWidth, $newHeight ){
624
+
625
+ $this->_resize($newWidth, $newHeight);
626
+
627
+ return $this;
628
+ }
629
+
630
+ /**
631
+ * Resize image to exact height. Width is auto calculated. Useful for creating row of images with the same height.
632
+ *
633
+ * @param int $newHeight Height in pixels.
634
+ *
635
+ * @return self
636
+ */
637
+ public function resizeExactHeight( $newHeight ){
638
+
639
+ $width = $this->image->getWidth();
640
+ $height = $this->image->getHeight();
641
+ $ratio = $width / $height;
642
+
643
+ $resizeHeight = $newHeight;
644
+ $resizeWidth = $newHeight * $ratio;
645
+
646
+ $this->_resize( $resizeWidth, $resizeHeight );
647
+
648
+ return $this;
649
+ }
650
+
651
+ /**
652
+ * Resize image to exact width. Height is auto calculated. Useful for creating column of images with the same width.
653
+ *
654
+ * @param int $newWidth Width in pixels.
655
+ *
656
+ * @return self
657
+ */
658
+ public function resizeExactWidth( $newWidth ){
659
+
660
+ $width = $this->image->getWidth();
661
+ $height = $this->image->getHeight();
662
+ $ratio = $width / $height;
663
+
664
+ $resizeWidth = $newWidth;
665
+ $resizeHeight = round($newWidth / $ratio);
666
+
667
+ $this->_resize( $resizeWidth, $resizeHeight );
668
+
669
+ return $this;
670
+ }
671
+
672
+ /**
673
+ * Resize image to fill all the space in the given dimension. Excess parts are cropped.
674
+ *
675
+ * @param int $newWidth Width in pixels.
676
+ * @param int $newHeight Height in pixels.
677
+ *
678
+ * @return self
679
+ */
680
+ public function resizeFill( $newWidth, $newHeight ){
681
+ $width = $this->image->getWidth();
682
+ $height = $this->image->getHeight();
683
+ $ratio = $width / $height;
684
+
685
+ // Base optimum size on new width
686
+ $optimumWidth = $newWidth;
687
+ $optimumHeight = round($newWidth / $ratio);
688
+
689
+ if( ($optimumWidth < $newWidth) or ($optimumHeight < $newHeight) ){ // Oops, where trying to fill and there are blank areas
690
+ // So base optimum size on height instead
691
+ $optimumWidth = $newHeight * $ratio;
692
+ $optimumHeight = $newHeight;
693
+ }
694
+
695
+ $this->_resize( $optimumWidth, $optimumHeight);
696
+ $this->crop( $newWidth, $newHeight ); // Trim excess parts
697
+
698
+ return $this;
699
+ }
700
+
701
+ /**
702
+ * Resize image to fit inside the given dimension. No part of the image is lost.
703
+ *
704
+ * @param int $newWidth Width in pixels.
705
+ * @param int $newHeight Height in pixels.
706
+ *
707
+ * @return self
708
+ */
709
+ public function resizeFit( $newWidth, $newHeight ) {
710
+
711
+ $width = $this->image->getWidth();
712
+ $height = $this->image->getHeight();
713
+ $ratio = $width / $height;
714
+
715
+ // Try basing it on width first
716
+ $resizeWidth = $newWidth;
717
+ $resizeHeight = round($newWidth / $ratio);
718
+
719
+ if( ( $resizeWidth > $newWidth ) or ( $resizeHeight > $newHeight ) ){ // Oops, either with or height does not fit
720
+ // So base on height instead
721
+ $resizeHeight = $newHeight;
722
+ $resizeWidth = $newHeight * $ratio;
723
+ }
724
+
725
+ $this->_resize( $resizeWidth, $resizeHeight );
726
+
727
+ return $this;
728
+ }
729
+
730
+ /**
731
+ * Rotate an image counter-clockwise.
732
+ *
733
+ * @param int $angle The angle in degrees.
734
+ * @param Color|null $color The Color object containing the background color.
735
+ *
736
+ * @return EditorInterface An instance of image editor.
737
+ */
738
+ public function rotate( $angle, $color = null ){
739
+
740
+ $this->_imageCheck();
741
+
742
+ $color = ($color !== null) ? $color : new Color('#000000');
743
+ list( $r, $g, $b, $alpha ) = $color->getRgba();
744
+
745
+ $this->image->getCore()->rotateImage( new \ImagickPixel("rgba($r, $g, $b, $alpha)"), $angle * -1 );
746
+
747
+ return $this;
748
+ }
749
+
750
+ /**
751
+ * Save the image to an image format.
752
+ *
753
+ * @param string $file File path where to save the image.
754
+ * @param null|string $type Type of image. Can be null, "GIF", "PNG", or "JPEG".
755
+ * @param null|string $quality Quality of image. Applies to JPEG only. Accepts number 0 - 100 where 0 is lowest and 100 is the highest quality. Or null for default.
756
+ * @param bool|false $interlace Set to true for progressive JPEG. Applies to JPEG only.
757
+ * @param int $permission Default permission when creating non-existing target directory.
758
+ *
759
+ * @return Editor
760
+ * @throws \Exception
761
+ */
762
+ public function save( $file, $type = null, $quality = null, $interlace = false, $permission = 0755 ){
763
+
764
+ $this->_imageCheck();
765
+
766
+ if ( null === $type ) {
767
+
768
+ $type = $this->_getImageTypeFromFileName( $file ); // Null given, guess type from file extension
769
+ if ( ImageType::UNKNOWN === $type ) {
770
+ $type = $this->image->getType(); // Unknown result, use original image type
771
+ }
772
+ }
773
+
774
+ $targetDir = dirname( $file ); // $file's directory
775
+ if( false === is_dir( $targetDir ) ){ // Check if $file's directory exist
776
+ // Create and set default perms to 755
777
+ if( !mkdir( $targetDir, $permission, true ) ){
778
+ throw new \Exception( sprintf('Cannot create %s', $targetDir) );
779
+ }
780
+ }
781
+
782
+ switch ( $type ) {
783
+ case ImageType::GIF :
784
+ $this->image->getCore()->writeImages( $file, true ); // Support animated image. Eg. GIF
785
+ break;
786
+
787
+ case ImageType::PNG :
788
+ // PNG is lossless and does not need compression
789
+ $this->image->getCore()->setImageFormat($type);
790
+ $this->image->getCore()->writeImage( $file );
791
+ break;
792
+
793
+ default: // Defaults to jpeg
794
+ $quality = ( $quality === null ) ? 60 : $quality; // Default to 60 when null given
795
+ $quality = ( $quality > 100 ) ? 100 : $quality;
796
+ $quality = ( $quality < 0 ) ? 0 : $quality;
797
+
798
+ if($interlace){
799
+ $this->image->getCore()->setImageInterlaceScheme( \Imagick::INTERLACE_JPEG );
800
+ }
801
+ $this->image->getCore()->setImageFormat($type);
802
+ $this->image->getCore()->setImageCompressionQuality($quality);
803
+ $this->image->getCore()->writeImage( $file ); // Single frame image. Eg. JPEG
804
+ }
805
+ return $this;
806
+ }
807
+
808
+ /**
809
+ * @param Image $image
810
+ */
811
+ public function setImage( $image ) {
812
+ $this->image = $image;
813
+ }
814
+
815
+ /**
816
+ * Write text to image.
817
+ *
818
+ * @param string $text The text to be written.
819
+ * @param int $size The font size. Defaults to 12.
820
+ * @param int $x The distance from the left edge of the image to the left of the text. Defaults to 0.
821
+ * @param int $y The distance from the top edge of the image to the top of the text. Defaults to 12 (equal to font size) so that the text is placed within the image.
822
+ * @param Color $color The Color object. Default text color is black.
823
+ * @param string $font Full path to font file. If blank, will default to Liberation Sans font.
824
+ * @param int $angle Angle of text from 0 - 359. Defaults to 0.
825
+ *
826
+ * @return EditorInterface
827
+ * @throws \Exception
828
+ */
829
+ public function text( $text, $size = 12, $x = 0, $y = 0, $color = null, $font = '', $angle = 0 ) {
830
+
831
+ $this->_imageCheck();
832
+
833
+ $y += $size;
834
+
835
+ $color = ($color !== null) ? $color : new Color('#000000');
836
+ $font = ($font !== '') ? $font : Grafika::fontsDir().DIRECTORY_SEPARATOR.'LiberationSans-Regular.ttf';
837
+
838
+ list( $r, $g, $b, $alpha ) = $color->getRgba();
839
+
840
+ // Set up draw properties
841
+ $draw = new \ImagickDraw();
842
+ // Text color
843
+ $draw->setFillColor( new \ImagickPixel("rgba($r, $g, $b, $alpha)") );
844
+ // Font properties
845
+ $draw->setFont( $font );
846
+ $draw->setFontSize( $size );
847
+
848
+ // Write text
849
+ $this->image->getCore()->annotateImage(
850
+ $draw,
851
+ $x,
852
+ $y,
853
+ $angle,
854
+ $text
855
+ );
856
+
857
+ return $this;
858
+ }
859
+
860
+ /**
861
+ * Get difference hash of image.
862
+ * Algorithm:
863
+ * Reduce size. The fastest way to remove high frequencies and detail is to shrink the image. In this case, shrink it to 9x8 so that there are 72 total pixels.
864
+ * Reduce color. Convert the image to a grayscale picture. This changes the hash from 72 pixels to a total of 72 colors.
865
+ * Compute the difference. The algorithm works on the difference between adjacent pixels. This identifies the relative gradient direction. In this case, the 9 pixels per row yields 8 differences between adjacent pixels. Eight rows of eight differences becomes 64 bits.
866
+ * Assign bits. Each bit is simply set based on whether the left pixel is brighter than the right pixel.
867
+ *
868
+ * http://www.hackerfactor.com/blog/index.php?/archives/529-Kind-of-Like-That.html
869
+ * @param Image $image
870
+ * @return string
871
+ */
872
+ private function _differenceHash($image)
873
+ {
874
+
875
+ $width = 9;
876
+ $height = 8;
877
+
878
+ $editor = new Editor();
879
+ $editor->setImage($image);
880
+ $editor->resizeExact( $width, $height); // Resize to exactly 9x8
881
+ $imagick = $editor->getImage()->getCore();
882
+
883
+ // Build hash
884
+ $hash = '';
885
+ for ($y = 0; $y < $height; $y++) {
886
+ // Get the pixel value for the leftmost pixel.
887
+ $rgba = $imagick->getImagePixelColor( 0, $y )->getColor();
888
+
889
+ $left = floor(($rgba['r'] + $rgba['g'] + $rgba['b']) / 3);
890
+ for ($x = 1; $x < $width; $x++) {
891
+ // Get the pixel value for each pixel starting from position 1.
892
+ $rgba = $imagick->getImagePixelColor( $x, $y )->getColor();
893
+ $right = floor(($rgba['r'] + $rgba['g'] + $rgba['b']) / 3);
894
+ // Each hash bit is set based on whether the left pixel is brighter than the right pixel.
895
+ if ($left > $right) {
896
+ $hash .= '1';
897
+ } else {
898
+ $hash .= '0';
899
+ }
900
+ // Prepare the next loop.
901
+ $left = $right;
902
+ }
903
+ }
904
+
905
+ return $hash;
906
+ }
907
+
908
+ /**
909
+ * Resize helper function.
910
+ *
911
+ * @param int $newWidth
912
+ * @param int $newHeight
913
+ *
914
+ * @return self
915
+ * @throws \Exception
916
+ */
917
+ private function _resize( $newWidth, $newHeight ){
918
+ $this->_imageCheck();
919
+
920
+ if ( 'GIF' == $this->image->getType() ) { // Animated image. Eg. GIF
921
+
922
+ $imagick = $this->image->getCore()->coalesceImages();
923
+
924
+ foreach ($imagick as $frame) {
925
+ $frame->resizeImage($newWidth, $newHeight, \Imagick::FILTER_BOX, 1, false);
926
+ $frame->setImagePage($newWidth, $newHeight, 0, 0);
927
+ }
928
+
929
+ // Assign new image with frames
930
+ $this->image = new Image($imagick->deconstructImages(), $this->image->getImageFile(), $newWidth, $newHeight, $this->image->getType());
931
+ } else { // Single frame image. Eg. JPEG, PNG
932
+
933
+ $this->image->getCore()->resizeImage($newWidth, $newHeight, \Imagick::FILTER_LANCZOS, 1, false);
934
+ // Assign new image
935
+ $this->image = new Image($this->image->getCore(), $this->image->getImageFile(), $newWidth, $newHeight, $this->image->getType());
936
+ }
937
+
938
+ }
939
+
940
+ /**
941
+ * Get image type base on file extension.
942
+ *
943
+ * @param int $imageFile File path to image.
944
+ *
945
+ * @return ImageType string Type of image.
946
+ */
947
+ private function _getImageTypeFromFileName( $imageFile ) {
948
+ $ext = strtolower( (string) pathinfo( $imageFile, PATHINFO_EXTENSION ) );
949
+
950
+ if ( 'jpg' == $ext or 'jpeg' == $ext ) {
951
+ return ImageType::JPEG;
952
+
953
+ } else if ( 'gif' == $ext ) {
954
+ return ImageType::GIF;
955
+
956
+ } else if ( 'png' == $ext ) {
957
+ return ImageType::PNG;
958
+
959
+ } else {
960
+ return ImageType::UNKNOWN;
961
+ }
962
+ }
963
+
964
+ /**
965
+ * Check if editor has already been assigned an image.
966
+ *
967
+ * @throws \Exception
968
+ */
969
+ private function _imageCheck(){
970
+ if( null === $this->image ){
971
+ throw new \Exception('No image to edit.');
972
+ }
973
+ }
974
+
975
+ }
src/CycloneSlider/Grafika/Imagick/Effect/Dither.php ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ namespace CycloneSlider\Grafika\Imagick\Effect;
4
+
5
+ use CycloneSlider\Grafika\EffectInterface;
6
+ use CycloneSlider\Grafika\Imagick\Image;
7
+
8
+ /**
9
+ * Dither image using Floyd-Steinberg algorithm. Dithering will turn the image black and white and add noise.
10
+ */
11
+ class Dither implements EffectInterface{
12
+
13
+
14
+ /**
15
+ * @param Image $image
16
+ *
17
+ * @return Image
18
+ */
19
+ public function apply( $image ) {
20
+ return $this->floydSteinberg( $image );
21
+ }
22
+
23
+ /**
24
+ * @param Image $image
25
+ *
26
+ * @return Image
27
+ */
28
+ private function floydSteinberg( $image ){
29
+ $pixels = array();
30
+
31
+ // Localize vars
32
+ $width = $image->getWidth();
33
+ $height = $image->getHeight();
34
+
35
+ // Loop using image1
36
+ $pixelIterator = $image->getCore()->getPixelIterator();
37
+ foreach ($pixelIterator as $y => $rows) { /* Loop through pixel rows */
38
+ foreach ( $rows as $x => $px ) { /* Loop through the pixels in the row (columns) */
39
+ /**
40
+ * @var $px \ImagickPixel */
41
+ $rgba = $px->getColor();
42
+
43
+ $gray = round($rgba['r'] * 0.3 + $rgba['g'] * 0.59 + $rgba['b'] * 0.11);
44
+
45
+ if(isset($pixels[$x][$y])){ // Add errors to color if there are
46
+ $gray += $pixels[$x][$y];
47
+ }
48
+
49
+ if ( $gray <= 127 ) { // Determine if black or white. Also has the benefit of clipping excess val due to adding the error
50
+ $blackOrWhite = 0;
51
+ } else {
52
+ $blackOrWhite = 255;
53
+ }
54
+
55
+ $oldPixel = $gray;
56
+ $newPixel = $blackOrWhite;
57
+
58
+ // Current pixel
59
+ $px->setColor("rgb($newPixel,$newPixel,$newPixel)");
60
+
61
+ $qError = $oldPixel - $newPixel; // Quantization error
62
+
63
+ // Propagate error on neighbor pixels
64
+ if ( $x + 1 < $width ) {
65
+ $pixels[$x+1][$y] = (isset($pixels[$x+1][$y]) ? $pixels[$x+1][$y] : 0) + ($qError * (7 / 16));
66
+ }
67
+
68
+ if ( $x - 1 > 0 and $y + 1 < $height ) {
69
+ $pixels[$x-1][$y+1] = (isset($pixels[$x-1][$y+1]) ? $pixels[$x-1][$y+1] : 0) + ($qError * (3 / 16));
70
+ }
71
+
72
+ if ( $y + 1 < $height ) {
73
+ $pixels[$x][$y+1] = (isset($pixels[$x][$y+1]) ? $pixels[$x][$y+1] : 0) + ($qError * (5 / 16));
74
+ }
75
+
76
+ if ( $x + 1 < $width and $y + 1 < $height ) {
77
+ $pixels[$x+1][$y+1] = (isset($pixels[$x+1][$y+1]) ? $pixels[$x+1][$y+1] : 0) + ($qError * (1 / 16));
78
+ }
79
+
80
+ }
81
+ $pixelIterator->syncIterator(); /* Sync the iterator, this is important to do on each iteration */
82
+ }
83
+
84
+ $type = $image->getType();
85
+ $file = $image->getImageFile();
86
+ $image = $image->getCore();
87
+
88
+ return new Image( $image, $file, $width, $height, $type ); // Create new image with updated core
89
+
90
+ }
91
+ }
src/CycloneSlider/Grafika/Imagick/Image.php ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ namespace CycloneSlider\Grafika\Imagick;
3
+
4
+ use CycloneSlider\Grafika\ImageInterface;
5
+
6
+ /**
7
+ * Immutable image class for Imagick.
8
+ * @package Grafika\Gd
9
+ */
10
+ final class Image implements ImageInterface {
11
+
12
+ /**
13
+ * @var \Imagick Imagick instance
14
+ */
15
+ private $imagick;
16
+
17
+ /**
18
+ * @var string File path to image
19
+ */
20
+ private $imageFile;
21
+
22
+ /**
23
+ * @var int Image width in pixels
24
+ */
25
+ private $width;
26
+
27
+ /**
28
+ * @var int Image height in pixels
29
+ */
30
+ private $height;
31
+
32
+ /**
33
+ * @var string Image type. Return value of Imagick::queryFormats(). See http://phpimagick.com/Imagick/queryFormats
34
+ * Sample values: JPEG, PNG, GIF, WBMP
35
+ */
36
+ private $type;
37
+
38
+ /**
39
+ * Image constructor.
40
+ *
41
+ * @param \Imagick $imagick
42
+ * @param string $imageFile
43
+ * @param int $width
44
+ * @param int $height
45
+ * @param string $type
46
+ */
47
+ public function __construct( \Imagick $imagick, $imageFile, $width, $height, $type ) {
48
+ $this->imagick = $imagick;
49
+ $this->imageFile = $imageFile;
50
+ $this->width = $width;
51
+ $this->height = $height;
52
+ $this->type = $type;
53
+ }
54
+
55
+
56
+ /**
57
+ * @param $imageFile
58
+ *
59
+ * @return Image
60
+ * @throws \Exception
61
+ */
62
+ public static function createFromFile( $imageFile ){
63
+ $imageFile = realpath( $imageFile );
64
+
65
+ if ( ! file_exists( $imageFile ) ) {
66
+ throw new \Exception( sprintf('Could not open image file "%s"', $imageFile) );
67
+ }
68
+
69
+ $imagick = new \Imagick( realpath($imageFile) );
70
+ return new self( $imagick, $imageFile, $imagick->getImageWidth(), $imagick->getImageHeight(), $imagick->getImageFormat());
71
+ }
72
+
73
+ /**
74
+ * Create a blank image.
75
+ *
76
+ * @param int $width Width in pixels.
77
+ * @param int $height Height in pixels.
78
+ *
79
+ * @return self
80
+ */
81
+ public static function createBlank($width = 1, $height = 1){
82
+ $imagick = new \Imagick();
83
+ $imagick->newImage($width, $height, new \ImagickPixel('black'));
84
+ $imagick->setImageFormat('png'); // Default to PNG.
85
+
86
+ return new self( $imagick, '', $imagick->getImageWidth(), $imagick->getImageHeight(), $imagick->getImageFormat());
87
+
88
+ }
89
+
90
+ /**
91
+ * Get Imagick instance
92
+ *
93
+ * @return \Imagick
94
+ */
95
+ public function getCore() {
96
+ return $this->imagick;
97
+ }
98
+
99
+ /**
100
+ * Get image file path.
101
+ *
102
+ * @return string File path to image.
103
+ */
104
+ public function getImageFile() {
105
+ return $this->imageFile;
106
+ }
107
+
108
+ /**
109
+ * Get image width in pixels.
110
+ *
111
+ * @return int
112
+ */
113
+ public function getWidth() {
114
+ return $this->width;
115
+ }
116
+
117
+ /**
118
+ * Get image height in pixels.
119
+ *
120
+ * @return int
121
+ */
122
+ public function getHeight() {
123
+ return $this->height;
124
+ }
125
+
126
+ /**
127
+ * Get image type.
128
+ *
129
+ * @return string
130
+ */
131
+ public function getType() {
132
+ return $this->type;
133
+ }
134
+
135
+ }
src/CycloneSlider/ImageResizer.php CHANGED
@@ -22,7 +22,6 @@ class CycloneSlider_ImageResizer {
22
  *
23
  * @return bool
24
  * @throws Exception
25
- * @internal param int $slider_id Slider post ID
26
  */
27
  public function resize_images( $slider_settings, $slides ){
28
 
@@ -101,19 +100,41 @@ class CycloneSlider_ImageResizer {
101
  * @return boolean True or false
102
  */
103
  private function resize_slide_image( $image_file, $image_file_dest, $width, $height, $resize_option, $resize_quality){
104
- // Create
105
- $image = new $this->image_editor;
106
- $image->set_file( $image_file );
107
- // Load
108
- if( $image->load() ){
109
-
110
- // Do resize
111
- $image->resize( $width, $height, $resize_option );
112
- $image->save( $image_file_dest, $resize_quality );
113
-
 
 
 
 
 
 
 
 
 
114
  return true;
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  }
116
- return false;
117
  }
118
 
119
  /**
22
  *
23
  * @return bool
24
  * @throws Exception
 
25
  */
26
  public function resize_images( $slider_settings, $slides ){
27
 
100
  * @return boolean True or false
101
  */
102
  private function resize_slide_image( $image_file, $image_file_dest, $width, $height, $resize_option, $resize_quality){
103
+ if(version_compare(PHP_VERSION, '5.3', '>=')){
104
+
105
+ $editor = \CycloneSlider\Grafika\Grafika::createEditor();
106
+ $editor->open( $image_file );
107
+ if('fill'==$resize_option){
108
+ $editor->resizeFill( $width, $height );
109
+ } else if('crop'==$resize_option){
110
+ $editor->crop( $width, $height );
111
+ } else if('exact'==$resize_option){
112
+ $editor->resizeExact( $width, $height );
113
+ } else if('exactHeight'==$resize_option){
114
+ $editor->resizeExactHeight( $height );
115
+ } else if('exactWidth'==$resize_option){
116
+ $editor->resizeExactWidth( $width );
117
+ } else {
118
+ $editor->resizeFit( $width, $height );
119
+ }
120
+ $editor->save( $image_file_dest, null, $resize_quality );
121
+
122
  return true;
123
+ } else {
124
+ // Create
125
+ $image = new CycloneSlider_ImageEditor($image_file);
126
+ // Load
127
+ if( $image->load() ){
128
+
129
+ // Do resize
130
+ $image->resize( $width, $height, $resize_option );
131
+ $image->save( $image_file_dest, $resize_quality );
132
+
133
+ return true;
134
+ }
135
+ return false;
136
  }
137
+
138
  }
139
 
140
  /**
src/autoloader.php ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+ // Autoloader
3
+ function cycloneslider_autoloader( $class_name ) {
4
+ if ( 0 === strpos( $class_name, 'CycloneSlider' ) ) {
5
+ $src = dirname( __FILE__ ) . DIRECTORY_SEPARATOR;
6
+ $class = str_replace( '_', DIRECTORY_SEPARATOR, $class_name ) . '.php';
7
+ require_once $src . $class;
8
+ }
9
+ }
10
+
11
+ spl_autoload_register( 'cycloneslider_autoloader' );
views/slide-edit.php CHANGED
@@ -40,7 +40,7 @@
40
  <div class="cs-image-preview">
41
  <div class="cs-image-thumb" <?php echo (empty($image_url)) ? 'style="display:none"' : '';?>>
42
  <?php if($image_url): ?>
43
- <img src="<?php echo esc_url($image_url); ?>" alt="thumb">
44
  <?php endif; ?>
45
  </div>
46
  <input class="cs-image-id" name="cycloneslider_metas[<?php echo esc_attr($i); ?>][id]" type="hidden" value="<?php echo esc_attr($slide['id']); ?>" />
40
  <div class="cs-image-preview">
41
  <div class="cs-image-thumb" <?php echo (empty($image_url)) ? 'style="display:none"' : '';?>>
42
  <?php if($image_url): ?>
43
+ <a target="_blank" href="<?php echo esc_url($full_image_url); ?>"><img src="<?php echo esc_url($image_url); ?>" alt="thumb"></a>
44
  <?php endif; ?>
45
  </div>
46
  <input class="cs-image-id" name="cycloneslider_metas[<?php echo esc_attr($i); ?>][id]" type="hidden" value="<?php echo esc_attr($slide['id']); ?>" />
views/slider-advanced-settings.php CHANGED
@@ -57,11 +57,20 @@
57
  <?php endforeach; ?>
58
  </select>
59
  <span class="note">
60
- <?php _e('Auto - Cyclone Slider decides the resize option.', 'cyclone-slider-2'); ?><br>
61
- <?php _e('Crop - Resize and remove excess parts.', 'cyclone-slider-2'); ?><br>
62
- <?php _e('Exact - Resize to exact dimensions.', 'cyclone-slider-2'); ?><br>
63
- <?php _e('Landscape - Resize to exact width.', 'cyclone-slider-2'); ?><br>
64
- <?php _e('Portrait - Resize to exact height.', 'cyclone-slider-2'); ?><br>
 
 
 
 
 
 
 
 
 
65
  </span>
66
  <div class="clear"></div>
67
  </div>
57
  <?php endforeach; ?>
58
  </select>
59
  <span class="note">
60
+ <?php if(version_compare(PHP_VERSION, '5.3', '>=')){ ?>
61
+ <?php _e('Fit - (Default) Fit images inside the slideshow maintaining aspect ratio.', 'cyclone-slider-2'); ?><br>
62
+ <?php _e('Fill - Images will fill the entire slideshow with no empty space.', 'cyclone-slider-2'); ?><br>
63
+ <?php _e('Crop - Excess parts of images are removed.', 'cyclone-slider-2'); ?><br>
64
+ <?php _e('Exact - Resize images to exact dimensions ignoring aspect ratio.', 'cyclone-slider-2'); ?><br>
65
+ <?php _e('Exact Width - Resize to exact width.', 'cyclone-slider-2'); ?><br>
66
+ <?php _e('Exact Height - Resize to exact height.', 'cyclone-slider-2'); ?><br>
67
+ <?php } else { ?>
68
+ <?php _e('Auto - Cyclone Slider decides the resize option.', 'cyclone-slider-2'); ?><br>
69
+ <?php _e('Crop - Resize and remove excess parts.', 'cyclone-slider-2'); ?><br>
70
+ <?php _e('Exact - Resize to exact dimensions.', 'cyclone-slider-2'); ?><br>
71
+ <?php _e('Landscape - Resize to exact width.', 'cyclone-slider-2'); ?><br>
72
+ <?php _e('Portrait - Resize to exact height.', 'cyclone-slider-2'); ?><br>
73
+ <?php } ?>
74
  </span>
75
  <div class="clear"></div>
76
  </div>