image_url = esc_url_raw( $image_url ); $this->image_url = trim( $this->image_url ); $this->image_url = apply_filters( 'tonesque_image_url', $this->image_url ); $this->image_obj = self::imagecreatefromurl( $this->image_url ); } /** * Build an image resource for a url. * * Images in this site's media library are read from disk. Anything else * is read over http and held only in memory. * * @param string $image_url Url to an image. * @return resource|GdImage|false */ public static function imagecreatefromurl( $image_url ) { $path = self::get_local_path( $image_url ); if ( '' !== $path ) return self::create_image_from_path( $path ); $body = self::get_remote_image_body( $image_url ); if ( ! self::is_supported_image_data( $body ) ) return false; $size = getimagesizefromstring( $body ); return self::create_image_from_string( $body, $size ); } /** * Image types this class is prepared to decode. * * @return array IMAGETYPE_* constants. */ private static function get_supported_image_types() { $types = array( IMAGETYPE_GIF, IMAGETYPE_PNG, IMAGETYPE_JPEG ); if ( defined( 'IMAGETYPE_WEBP' ) ) $types[] = IMAGETYPE_WEBP; return $types; } /** * Host and port of a url, as "host" or "host:port". * * @param string $url Url to read. * @return string Lowercased origin, or an empty string when there is no host. */ private static function get_url_origin( $url ) { $host = wp_parse_url( $url, PHP_URL_HOST ); if ( empty( $host ) ) return ''; $origin = strtolower( $host ); $port = absint( wp_parse_url( $url, PHP_URL_PORT ) ); if ( empty( $port ) ) return $origin; $scheme = strtolower( (string) wp_parse_url( $url, PHP_URL_SCHEME ) ); if ( ( 'http' === $scheme && 80 === $port ) || ( 'https' === $scheme && 443 === $port ) ) return $origin; return $origin . ':' . $port; } /** * Whether a url is served by this site. * * @param string $url Url to test. * @return bool */ private static function is_local_url( $url ) { $url = trim( (string) $url ); if ( '' === $url ) return false; if ( 0 === strpos( $url, '/' ) && 0 !== strpos( $url, '//' ) ) return true; $scheme = wp_parse_url( $url, PHP_URL_SCHEME ); if ( ! empty( $scheme ) && ! in_array( strtolower( $scheme ), array( 'http', 'https' ), true ) ) return false; $origin = self::get_url_origin( $url ); if ( '' === $origin ) return false; $origins = array(); $uploads = wp_get_upload_dir(); $urls = array( home_url(), site_url() ); if ( ! empty( $uploads['baseurl'] ) ) $urls[] = $uploads['baseurl']; foreach ( $urls as $candidate ) { $candidate_origin = self::get_url_origin( $candidate ); if ( '' !== $candidate_origin ) $origins[] = $candidate_origin; } return in_array( $origin, $origins, true ); } /** * Readable path of the media library file behind a url, when there is one. * * @param string $url Url to an image. * @return string Path to the file, or an empty string. */ private static function get_local_path( $url ) { if ( ! self::is_local_url( $url ) ) return ''; list( $url ) = explode( '#', $url ); list( $url ) = explode( '?', $url ); if ( 0 === strpos( $url, '/' ) && 0 !== strpos( $url, '//' ) ) $url = home_url( $url ); $candidates = array( $url ); /* * Post content usually references an intermediate size. Only the full * size url is stored against the attachment, and uploads over the big * image threshold are stored with a "-scaled" suffix. */ $full_url = preg_replace( '/-\d+x\d+(?=\.[a-zA-Z0-9]{2,5}$)/', '', $url ); if ( $full_url !== $url ) { $candidates[] = $full_url; $candidates[] = preg_replace( '/(\.[a-zA-Z0-9]{2,5})$/', '-scaled$1', $full_url ); } $attachment_id = 0; foreach ( $candidates as $candidate ) { $attachment_id = absint( attachment_url_to_postid( $candidate ) ); if ( ! empty( $attachment_id ) ) break; } if ( empty( $attachment_id ) ) return ''; if ( 0 !== strpos( (string) get_post_mime_type( $attachment_id ), 'image/' ) ) return ''; $file = get_attached_file( $attachment_id ); $uploads = wp_get_upload_dir(); if ( empty( $file ) || empty( $uploads['basedir'] ) ) return ''; $path = realpath( $file ); $basedir = realpath( $uploads['basedir'] ); if ( false === $path || false === $basedir ) return ''; if ( 0 !== strpos( $path, rtrim( $basedir, DIRECTORY_SEPARATOR ) . DIRECTORY_SEPARATOR ) ) return ''; if ( ! is_file( $path ) || ! is_readable( $path ) ) return ''; return $path; } /** * Read an image over http. * * The response is capped and is only ever held in memory - nothing is * written to disk or added to the media library. * * @param string $url Url to request. * @return string Raw image data, or an empty string. */ private static function get_remote_image_body( $url ) { $url = trim( (string) $url ); if ( '' === $url ) return ''; if ( 0 === strpos( $url, '//' ) ) $url = ( is_ssl() ? 'https:' : 'http:' ) . $url; elseif ( 0 === strpos( $url, '/' ) ) $url = home_url( $url ); $response = wp_safe_remote_get( $url, array( 'timeout' => self::REMOTE_IMAGE_TIMEOUT, 'redirection' => 3, 'limit_response_size' => self::REMOTE_IMAGE_MAX_BYTES, ) ); if ( is_wp_error( $response ) ) return ''; if ( 200 != wp_remote_retrieve_response_code( $response ) ) return ''; $type = wp_remote_retrieve_header( $response, 'content-type' ); if ( is_array( $type ) ) $type = reset( $type ); if ( 0 !== strpos( strtolower( (string) $type ), 'image/' ) ) return ''; $body = wp_remote_retrieve_body( $response ); if ( ! is_string( $body ) || '' === $body ) return ''; /* A response that reached the cap was cut short and cannot be trusted. */ if ( strlen( $body ) >= self::REMOTE_IMAGE_MAX_BYTES ) return ''; return $body; } /** * Whether data begins with the signature of a format this class decodes. * * @param string $body Raw image data. * @return bool */ private static function is_supported_image_data( $body ) { if ( ! is_string( $body ) || strlen( $body ) < 12 ) return false; /* JPEG */ if ( "\xFF\xD8\xFF" === substr( $body, 0, 3 ) ) return true; /* PNG */ if ( "\x89PNG\r\n\x1A\n" === substr( $body, 0, 8 ) ) return true; /* GIF */ if ( 'GIF87a' === substr( $body, 0, 6 ) || 'GIF89a' === substr( $body, 0, 6 ) ) return true; /* WebP */ if ( 'RIFF' === substr( $body, 0, 4 ) && 'WEBP' === substr( $body, 8, 4 ) ) return true; return false; } /** * Build an image resource from a file in the uploads directory. * * @param string $path Path to the file. * @return resource|GdImage|false */ private static function create_image_from_path( $path ) { $size = wp_getimagesize( $path ); if ( ! is_array( $size ) || empty( $size[2] ) ) return false; if ( ! in_array( $size[2], self::get_supported_image_types(), true ) ) return false; switch ( $size[2] ) { case IMAGETYPE_GIF : return function_exists( 'imagecreatefromgif' ) ? imagecreatefromgif( $path ) : false; case IMAGETYPE_PNG : return function_exists( 'imagecreatefrompng' ) ? imagecreatefrompng( $path ) : false; case IMAGETYPE_JPEG : return function_exists( 'imagecreatefromjpeg' ) ? imagecreatefromjpeg( $path ) : false; } if ( function_exists( 'imagecreatefromwebp' ) ) return imagecreatefromwebp( $path ); return false; } /** * Build an image resource from data already held in memory. * * @param string $body Raw image data. * @param array $size Result of getimagesizefromstring() for that data. * @return resource|GdImage|false */ private static function create_image_from_string( $body, $size ) { if ( ! is_array( $size ) || empty( $size[2] ) ) return false; if ( ! in_array( $size[2], self::get_supported_image_types(), true ) ) return false; if ( ! function_exists( 'imagecreatefromstring' ) ) return false; return imagecreatefromstring( $body ); } /** * * Construct object from image. * * @param optional $type (hex, rgb, hsl) * @return color as a string formatted as $type * */ function color( $type = 'hex' ) { // Bail if there is no image to work with if ( ! $this->image_obj ) return false; // Finds dominant color $color = self::grab_color(); // Passes value to Color class $color = self::get_color( $color, $type ); return $color; } /** * * Grabs the color index for each of five sample points of the image * * @param $image * @param $type can be 'index' or 'hex' * @return array() with color indices * */ function grab_points( $type = 'index' ) { $img = $this->image_obj; if ( ! $img ) return false; $height = imagesy( $img ); $width = imagesx( $img ); // Sample five points in the image // Based on rule of thirds and center $topy = round( $height / 3 ); $bottomy = round( ( $height / 3 ) * 2 ); $leftx = round( $width / 3 ); $rightx = round( ( $width / 3 ) * 2 ); $centery = round( $height / 2 ); $centerx = round( $width / 2 ); // Cast those colors into an array $points = array( imagecolorat( $img, $leftx, $topy ), imagecolorat( $img, $rightx, $topy ), imagecolorat( $img, $leftx, $bottomy ), imagecolorat( $img, $rightx, $bottomy ), imagecolorat( $img, $centerx, $centery ), ); if ( 'hex' == $type ) { foreach ( $points as $i => $p ) { $c = imagecolorsforindex( $img, $p ); $points[ $i ] = self::get_color( array( 'r' => $c['red'], 'g' => $c['green'], 'b' => $c['blue'], ), 'hex' ); } } return $points; } /** * * Finds the average color of the image based on five sample points * * @param $image * @return array() with rgb color * */ function grab_color() { $img = $this->image_obj; if ( ! $img ) return false; $rgb = self::grab_points(); // Process the color points // Find the average representation foreach ( $rgb as $color ) { $index = imagecolorsforindex( $img, $color ); $r[] = $index['red']; $g[] = $index['green']; $b[] = $index['blue']; $red = round( array_sum( $r ) / 5 ); $green = round( array_sum( $g ) / 5 ); $blue = round( array_sum( $b ) / 5 ); } // The average color of the image as rgb array $color = array( 'r' => $red, 'g' => $green, 'b' => $blue, ); return $color; } /** * * Get a Color object using /lib class.color * Convert to appropriate type * * @return string * */ function get_color( $color, $type ) { $c = new Jetpack_Color( $color, 'rgb' ); $this->color = $c; switch ( $type ) { case 'rgb' : $color = implode( ',', $c->toRgbInt() ); break; case 'hex' : $color = $c->toHex(); break; case 'hsv' : $color = implode( ',', $c->toHsvInt() ); break; default: return $color = $c->toHex(); } return $color; } /** * * Checks contrast against main color * Gives either black or white for using with opacity * * @return string * */ function contrast() { if ( ! $this->color ) return false; $c = $this->color->getMaxContrastColor(); return implode( ',', $c->toRgbInt() ); } };