Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 109
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
Tonesque
0.00% covered (danger)
0.00%
0 / 108
0.00% covered (danger)
0.00%
0 / 7
992
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 imagecreatefromurl
0.00% covered (danger)
0.00%
0 / 29
0.00% covered (danger)
0.00%
0 / 1
182
 color
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 grab_points
0.00% covered (danger)
0.00%
0 / 30
0.00% covered (danger)
0.00%
0 / 1
20
 grab_color
0.00% covered (danger)
0.00%
0 / 21
0.00% covered (danger)
0.00%
0 / 1
12
 get_color
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
30
 contrast
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
1<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
2/**
3 * Tonesque
4 * Grab an average color representation from an image.
5 *
6 * @author Automattic
7 * @author Matias Ventura
8 * @package automattic/jetpack
9 */
10
11if ( ! class_exists( 'Tonesque' ) ) {
12    /**
13     * Color representation class.
14     */
15    class Tonesque {
16        /**
17         * Image URL.
18         *
19         * @var string
20         */
21        private $image_url = '';
22        /**
23         * Image identifier representing the image.
24         *
25         * @var null|object
26         */
27        private $image_obj = null;
28        /**
29         * Color code.
30         *
31         * @var string
32         */
33        private $color = '';
34
35        /**
36         * Constructor.
37         *
38         * @param string $image_url Image URL.
39         */
40        public function __construct( $image_url ) {
41            _deprecated_function( 'Tonesque::__construct', 'jetpack-13.8' );
42
43            if ( ! class_exists( 'Jetpack_Color' ) ) {
44                require_once JETPACK__PLUGIN_DIR . '/_inc/lib/class.color.php';
45            }
46
47            $this->image_url = esc_url_raw( $image_url );
48            $this->image_url = trim( $this->image_url );
49            /**
50             * Allows any image URL to be passed in for $this->image_url.
51             *
52             * @module theme-tools
53             *
54             * @since 2.5.0
55             *
56             * @param string $image_url The URL to any image
57             */
58            $this->image_url = apply_filters( 'tonesque_image_url', $this->image_url );
59
60            $this->image_obj = self::imagecreatefromurl( $this->image_url );
61        }
62
63        /**
64         * Get an image object from a URL.
65         *
66         * @param string $image_url Image URL.
67         *
68         * @return object|bool Image object or false if the image could not be loaded.
69         */
70        public static function imagecreatefromurl( $image_url ) {
71            _deprecated_function( 'Tonesque::imagecreatefromurl', 'jetpack-13.8' );
72
73            $data = null;
74
75            // If it's a URL.
76            if ( preg_match( '#^https?://#i', $image_url ) ) {
77                // If it's a url pointing to a local media library url.
78                $content_url = content_url();
79                $_image_url  = set_url_scheme( $image_url );
80                if ( str_starts_with( $_image_url, $content_url ) ) {
81                    $_image_path = str_replace( $content_url, WP_CONTENT_DIR, $_image_url );
82                    if ( file_exists( $_image_path ) ) {
83                        $filetype = wp_check_filetype( $_image_path );
84                        $type     = $filetype['type'];
85
86                        if ( str_starts_with( $type, 'image/' ) ) {
87                            $data = file_get_contents( $_image_path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
88                        }
89                    }
90                }
91
92                if ( empty( $data ) ) {
93                    $response      = wp_safe_remote_get( $image_url );
94                    $response_code = wp_remote_retrieve_response_code( $response );
95                    if (
96                        is_wp_error( $response )
97                        || ! $response_code
98                        || $response_code < 200
99                        || $response_code >= 300
100                    ) {
101                        return false;
102                    }
103                    $data = wp_remote_retrieve_body( $response );
104                }
105            }
106
107            // If it's a local path in our WordPress install.
108            if ( file_exists( $image_url ) ) {
109                $filetype = wp_check_filetype( $image_url );
110                $type     = $filetype['type'];
111
112                if ( str_starts_with( $type, 'image/' ) ) {
113                    $data = file_get_contents( $image_url ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
114                }
115            }
116
117            if ( null === $data ) {
118                return false;
119            }
120
121            // Now turn it into an image and return it.
122            return imagecreatefromstring( $data );
123        }
124
125        /**
126         * Construct object from image.
127         *
128         * @param string $type Type (hex, rgb, hsv) (optional).
129         *
130         * @return string|bool color as a string formatted as $type or false if the image could not be loaded.
131         */
132        public function color( $type = 'hex' ) {
133            // Bail if there is no image to work with.
134            if ( ! $this->image_obj ) {
135                return false;
136            }
137
138            // Finds dominant color.
139            $color = self::grab_color();
140            // Passes value to Color class.
141            return self::get_color( $color, $type );
142        }
143
144        /**
145         * Grabs the color index for each of five sample points of the image
146         *
147         * @param string $type can be 'index' or 'hex'.
148         *
149         * @return array|false color indices or false if the image could not be loaded.
150         */
151        public function grab_points( $type = 'index' ) {
152            $img = $this->image_obj;
153            if ( ! $img ) {
154                return false;
155            }
156
157            $height = imagesy( $img );
158            $width  = imagesx( $img );
159
160            /*
161            * Sample five points in the image
162            * based on rule of thirds and center.
163            */
164            $topy    = (int) round( $height / 3 );
165            $bottomy = (int) round( ( $height / 3 ) * 2 );
166            $leftx   = (int) round( $width / 3 );
167            $rightx  = (int) round( ( $width / 3 ) * 2 );
168            $centery = (int) round( $height / 2 );
169            $centerx = (int) round( $width / 2 );
170
171            // Cast those colors into an array.
172            $points = array(
173                imagecolorat( $img, $leftx, $topy ),
174                imagecolorat( $img, $rightx, $topy ),
175                imagecolorat( $img, $leftx, $bottomy ),
176                imagecolorat( $img, $rightx, $bottomy ),
177                imagecolorat( $img, $centerx, $centery ),
178            );
179
180            if ( 'hex' === $type ) {
181                foreach ( $points as $i => $p ) {
182                    $c            = imagecolorsforindex( $img, $p );
183                    $points[ $i ] = self::get_color(
184                        array(
185                            'r' => $c['red'],
186                            'g' => $c['green'],
187                            'b' => $c['blue'],
188                        ),
189                        'hex'
190                    );
191                }
192            }
193
194            return $points;
195        }
196
197        /**
198         * Finds the average color of the image based on five sample points
199         *
200         * @return array|bool array with rgb color or false if the image could not be loaded.
201         */
202        public function grab_color() {
203            $img = $this->image_obj;
204            if ( ! $img ) {
205                return false;
206            }
207
208            $rgb = self::grab_points();
209
210            $r = array();
211            $g = array();
212            $b = array();
213
214            /*
215            * Process the color points
216            * Find the average representation
217            */
218            foreach ( $rgb as $color ) {
219                $index = imagecolorsforindex( $img, $color );
220                $r[]   = $index['red'];
221                $g[]   = $index['green'];
222                $b[]   = $index['blue'];
223            }
224            $red   = round( array_sum( $r ) / 5 );
225            $green = round( array_sum( $g ) / 5 );
226            $blue  = round( array_sum( $b ) / 5 );
227
228            // The average color of the image as rgb array.
229            $color = array(
230                'r' => $red,
231                'g' => $green,
232                'b' => $blue,
233            );
234
235            return $color;
236        }
237
238        /**
239         * Get a Color object using /lib class.color
240         * Convert to appropriate type
241         *
242         * @param string $color Color code.
243         * @param string $type  Color type (rgb, hex, hsv).
244         *
245         * @return string
246         */
247        public function get_color( $color, $type ) {
248            $c           = new Jetpack_Color( $color, 'rgb' );
249            $this->color = $c;
250
251            switch ( $type ) {
252                case 'rgb':
253                    $color = implode( ',', $c->toRgbInt() );
254                    break;
255                case 'hex':
256                    $color = $c->toHex();
257                    break;
258                case 'hsv':
259                    $color = implode( ',', $c->toHsvInt() );
260                    break;
261                default:
262                    return $c->toHex();
263            }
264
265            return $color;
266        }
267
268        /**
269         *
270         * Checks contrast against main color
271         * Gives either black or white for using with opacity
272         *
273         * @return string|bool Returns black or white or false if the image could not be loaded.
274         */
275        public function contrast() {
276            if ( ! $this->color ) {
277                return false;
278            }
279
280            $c = $this->color->getMaxContrastColor();
281            return implode( ',', $c->toRgbInt() );
282        }
283    }
284}