config = array_merge(\Config::get('image', array()), $config); } else { $this->config = \Config::get('image', array()); } $this->debug("Image Class was initialized using the " . $this->config['driver'] . " driver."); } /** * Accepts configuration in either an array (as $index) or a pairing using $index and $value * * @param string $index The index to be set, or an array of configuration options. * @param mixed $value The value to be set if $index is not an array. * @return Image_Driver */ public function config($index = null, $value = null) { if (is_array($index)) { if (isset($index['driver'])) { throw new \RuntimeException("The driver cannot be changed after initialization!"); } $this->config = array_merge($this->config, $index); } elseif ($index != null) { if ($index == 'driver') { throw new \RuntimeException("The driver cannot be changed after initialization!"); } $this->config[$index] = $value; } return $this; } /** * Executes the presets set in the config. Additional parameters replace the $1, $2, ect. * * @param string $name The name of the preset. * @return Image_Driver */ public function preset($name) { $vars = func_get_args(); if (isset($this->config['presets'][$name])) { $old_config = $this->config; $this->config = array_merge($this->config, $this->config['presets'][$name]); foreach ($this->config['actions'] AS $action) { $func = $action[0]; array_shift($action); for ($i = 0; $i < count($action); $i++) { for ($x = count($vars) - 1; $x >= 0; $x--) { $action[$i] = preg_replace('#\$' . $x . '#', $vars[$x], $action[$i]); } } call_fuel_func_array(array($this, $func), $action); } $this->config = $old_config; } else { throw new \InvalidArgumentException("Could not load preset $name, you sure it exists?"); } return $this; } /** * Loads the image and checks if its compatible. * * @param string $filename The file to load * @param string $return_data Decides if it should return the images data, or just "$this". * @param mixed $force_extension Decides if it should force the extension with this (or false) * @return Image_Driver */ public function load($filename, $return_data = false, $force_extension = false) { // First check if the filename exists $filename = realpath($filename); $return = array( 'filename' => $filename, 'return_data' => $return_data, ); if (is_file($filename)) { // Check the extension $ext = $this->check_extension($filename, false, $force_extension); if ($ext !== false) { $return = array_merge($return, array( 'image_fullpath' => $filename, 'image_directory' => dirname($filename), 'image_filename' => basename($filename), 'image_extension' => $ext, )); if ( ! $return_data) { $this->image_fullpath = $filename; $this->image_directory = dirname($filename); $this->image_filename = basename($filename); $this->image_extension = $ext; } } else { throw new \RuntimeException("The library does not support this filetype for $filename."); } } else { throw new \OutOfBoundsException("Image file $filename does not exist."); } return $return; } /** * Crops the image using coordinates or percentages. * * Positive whole numbers or percentages are coordinates from the top left. * * Negative whole numbers or percentages are coordinates from the bottom right. * * @param integer $x1 X-Coordinate for first set. * @param integer $y1 Y-Coordinate for first set. * @param integer $x2 X-Coordinate for second set. * @param integer $y2 Y-Coordinate for second set. * @return Image_Driver */ public function crop($x1, $y1, $x2, $y2) { $this->queue('crop', $x1, $y1, $x2, $y2); return $this; } /** * Executes the crop event when the queue is ran. * * Formats the crop method input for use with driver specific methods * * @param integer $x1 X-Coordinate for first set. * @param integer $y1 Y-Coordinate for first set. * @param integer $x2 X-Coordinate for second set. * @param integer $y2 Y-Coordinate for second set. * @return array An array of variables for the specific driver. */ protected function _crop($x1, $y1, $x2, $y2) { $y1 === null and $y1 = $x1; $x2 === null and $x2 = "-" . $x1; $y2 === null and $y2 = "-" . $y1; $x1 = $this->convert_number($x1, true); $y1 = $this->convert_number($y1, false); $x2 = $this->convert_number($x2, true); $y2 = $this->convert_number($y2, false); return array( 'x1' => $x1, 'y1' => $y1, 'x2' => $x2, 'y2' => $y2, ); } /** * Resize the image. If the width or height is null, it will resize retaining the original aspect ratio. * * @param integer $width The new width of the image. * @param integer $height The new height of the image. * @param boolean $keepar If false, allows stretching of the image. * @param boolean $pad Adds padding to the image when resizing. * @return Image_Driver */ public function resize($width, $height = null, $keepar = true, $pad = false) { $this->queue('resize', $width, $height, $keepar, $pad); return $this; } /** * Creates a vertical / horizontal or both mirror image. * * @param mixed $direction 'vertical', 'horizontal', 'both' * @return Image_Driver */ public function flip($direction) { $this->queue('flip', $direction); return $this; } /** * Executes the resize event when the queue is ran. * * Formats the resize method input for use with driver specific methods. * * @param integer $width The new width of the image. * @param integer $height The new height of the image. * @param boolean $keepar If false, allows stretching of the image. * @param boolean $pad Adds padding to the image when resizing. * @return array An array of variables for the specific driver. */ protected function _resize($width, $height = null, $keepar = true, $pad = true) { if ($height == null or $width == null) { if ($height == null and substr($width, -1) == '%') { $height = $width; } elseif (substr($height, -1) == '%' and $width == null) { $width = $height; } else { $sizes = $this->sizes(); if ($height == null and $width != null) { $height = $width * ($sizes->height / $sizes->width); } elseif ($height != null and $width == null) { $width = $height * ($sizes->width / $sizes->height); } else { throw new \InvalidArgumentException("Width and height cannot be null."); } } } $origwidth = $this->convert_number($width, true); $origheight = $this->convert_number($height, false); $width = $origwidth; $height = $origheight; $sizes = $this->sizes(); $x = 0; $y = 0; if ($keepar) { // See which is the biggest ratio if (function_exists('bcdiv')) { $width_ratio = bcdiv($width, $sizes->width, 10); $height_ratio = bcdiv($height, $sizes->height, 10); $compare = bccomp($width_ratio, $height_ratio, 10); if ($compare > -1) { $height = ceil((float) bcmul($sizes->height, $height_ratio, 10)); $width = ceil((float) bcmul($sizes->width, $height_ratio, 10)); } else { $height = ceil((float) bcmul($sizes->height, $width_ratio, 10)); $width = ceil((float) bcmul($sizes->width, $width_ratio, 10)); } } else { $width_ratio = $width / $sizes->width; $height_ratio = $height / $sizes->height; if ($width_ratio >= $height_ratio) { $height = ceil($sizes->height * $height_ratio); $width = ceil($sizes->width * $height_ratio); } else { $height = ceil($sizes->height * $width_ratio); $width = ceil($sizes->width * $width_ratio); } } } if ($pad) { $x = floor(($origwidth - $width) / 2); $y = floor(($origheight - $height) / 2); } else { $origwidth = $width; $origheight = $height; } return array( 'width' => $width, 'height' => $height, 'cwidth' => $origwidth, 'cheight' => $origheight, 'x' => $x, 'y' => $y, ); } public function crop_resize($width, $height = null) { is_null($height) and $height = $width; $this->queue('crop_resize', $width, $height); return $this; } protected function _crop_resize($width, $height) { // Determine the crop size $sizes = $this->sizes(); $width = $this->convert_number($width, true); $height = $this->convert_number($height, false); if (function_exists('bcdiv')) { if (bccomp(bcdiv($sizes->width, $width, 10), bcdiv($sizes->height, $height, 10), 10) < 1) { $this->_resize($width, 0, true, false); } else { $this->_resize(0, $height, true, false); } } else { if ($sizes->width / $width < $sizes->height / $height) { $this->_resize($width, 0, true, false); } else { $this->_resize(0, $height, true, false); } } $sizes = $this->sizes(); $y = floor(max(0, $sizes->height - $height) / 2); $x = floor(max(0, $sizes->width - $width) / 2); $this->_crop($x, $y, $x + $width, $y + $height); } /** * Rotates the image * * @param integer $degrees The degrees to rotate, negatives integers allowed. * @return Image_Driver */ public function rotate($degrees) { $this->queue('rotate', $degrees); return $this; } /** * Executes the rotate event when the queue is ran. * * Formats the rotate method input for use with driver specific methods * * @param integer $degrees The degrees to rotate, negatives integers allowed. * @return array An array of variables for the specific driver. */ protected function _rotate($degrees) { $degrees %= 360; if ($degrees < 0) { $degrees = 360 + $degrees; } return array( 'degrees' => $degrees, ); } /** * Adds a watermark to the image. * * @param string $filename The filename of the watermark file to use. * @param string $position The position of the watermark, ex: "bottom right", "center center", "top left" * @param integer $padding The amount of padding (in pixels) from the position. * @return Image_Driver */ public function watermark($filename, $position, $padding = 5) { $this->queue('watermark', $filename, $position, $padding); return $this; } /** * Executes the watermark event when the queue is ran. * * Formats the watermark method input for use with driver specific methods * * @param string $filename The filename of the watermark file to use. * @param string $position The position of the watermark, ex: "bottom right", "center center", "top left" * @param integer $padding The amount of padding (in pixels) from the position. * @return array An array of variables for the specific driver. */ protected function _watermark($filename, $position, $padding = 5) { $filename = realpath($filename); $return = false; if (is_file($filename) and $this->check_extension($filename, false)) { $x = 0; $y = 0; $wsizes = $this->sizes($filename); $sizes = $this->sizes(); // Get the x and y positions. list($ypos, $xpos) = explode(' ', $position); switch ($xpos) { case 'left': $x = $padding; break; case 'middle': case 'center': $x = ($sizes->width / 2) - ($wsizes->width / 2); break; case 'right': $x = $sizes->width - $wsizes->width - $padding; break; } switch ($ypos) { case 'top': $y = $padding; break; case 'middle': case 'center': $y = ($sizes->height / 2) - ($wsizes->height / 2); break; case 'bottom': $y = $sizes->height - $wsizes->height - $padding; break; } $this->debug("Watermark being placed at $x,$y"); $return = array( 'filename' => $filename, 'x' => $x, 'y' => $y, 'padding' => $padding, ); } return $return; } /** * Adds a border to the image. * * @param integer $size The side of the border, in pixels. * @param string $color A hexadecimal color. * @return Image_Driver */ public function border($size, $color = null) { $this->queue('border', $size, $color); return $this; } /** * Executes the border event when the queue is ran. * * Formats the border method input for use with driver specific methods * * @param integer $size The side of the border, in pixels. * @param string $color A hexadecimal color. * @return array An array of variables for the specific driver. */ protected function _border($size, $color = null) { empty($color) and $color = $this->config['bgcolor']; return array( 'size' => $size, 'color' => $color, ); } /** * Masks the image using the alpha channel of the image input. * * @param string $maskimage The location of the image to use as the mask * @return Image_Driver */ public function mask($maskimage) { $this->queue('mask', $maskimage); return $this; } /** * Executes the mask event when the queue is ran. * * Formats the mask method input for use with driver specific methods * * @param string $maskimage The location of the image to use as the mask * @return array An array of variables for the specific driver. */ protected function _mask($maskimage) { return array( 'maskimage' => $maskimage, ); } /** * Adds rounded corners to the image. * * @param integer $radius * @param integer $sides Accepts any combination of "tl tr bl br" separated by spaces, or null for all sides * @param integer $antialias Sets the anti-alias range. * @return Image_Driver */ public function rounded($radius, $sides = null, $antialias = null) { $this->queue('rounded', $radius, $sides, $antialias); return $this; } /** * Executes the rounded event when the queue is ran. * * Formats the rounded method input for use with driver specific methods * * @param integer $radius * @param integer $sides Accepts any combination of "tl tr bl br" separated by spaces, or null for all sides * @param integer $antialias Sets the anti-alias range. * @return array An array of variables for the specific driver. */ protected function _rounded($radius, $sides, $antialias) { $radius < 0 and $radius = 0; $tl = $tr = $bl = $br = $sides == null; if ($sides != null) { $sides = explode(' ', $sides); foreach ($sides as $side) { if ($side == 'tl' or $side == 'tr' or $side == 'bl' or $side == 'br') { $$side = true; } } } $antialias == null and $antialias = 1; return array( 'radius' => $radius, 'tl' => $tl, 'tr' => $tr, 'bl' => $bl, 'br' => $br, 'antialias' => $antialias, ); } /** * Turns the image into a grayscale version * * @return Image_Driver */ public function grayscale() { $this->queue('grayscale'); return $this; } /** * Executes the grayscale event when the queue is ran. */ abstract protected function _grayscale(); /** * Saves the image, and optionally attempts to set permissions * * @param string $filename The location where to save the image. * @param string $permissions Allows unix style permissions * @return array */ public function save($filename = null, $permissions = null) { if (empty($filename)) { $filename = $this->image_filename; } $directory = dirname($filename); if ( ! is_dir($directory)) { throw new \OutOfBoundsException("Could not find directory \"$directory\""); } if ( ! $this->check_extension($filename, true)) { $filename .= "." . $this->image_extension; } // Touch the file if ( ! touch($filename)) { throw new \RuntimeException("Do not have permission to write to \"$filename\""); } // Set the new permissions if ($permissions != null and ! chmod($filename, $permissions)) { throw new \RuntimeException("Could not set permissions on the file."); } $this->debug("", "Saving image as $filename"); return array( 'filename' => $filename, ); } /** * Saves the file in the original location, adding the append and prepend to the filename. * * @param string $append The string to append to the filename * @param string $prepend The string to prepend to the filename * @param string $extension The extension to save the image as, null defaults to the loaded images extension. * @param integer $permissions The permissions to attempt to set on the file. * @return Image_Driver */ public function save_pa($append, $prepend = null, $extension = null, $permissions = null) { $filename = substr($this->image_filename, 0, -(strlen($this->image_extension) + 1)); $fullpath = $this->image_directory.'/'.$append.$filename.$prepend.'.'. ($extension !== null ? $extension : $this->image_extension); $this->save($fullpath, $permissions); return $this; } /** * Outputs the file directly to the user. * * @param string $filetype The extension type to use. Ex: png, jpg, gif * @return array * @throws \FuelException */ public function output($filetype = null) { if ($filetype == null) { $filetype = $this->config['filetype'] == null ? $this->image_extension : $this->config['filetype']; } if ($this->check_extension($filetype, false)) { if ( ! $this->config['debug']) { $mimetype = $filetype === 'jpg' ? 'jpeg' : $filetype; header('Content-Type: image/' . $mimetype); } $this->new_extension = $filetype; } else { throw new \FuelException("Image extension $filetype is unsupported."); } $this->debug('', "Outputting image as $filetype"); return array( 'filetype' => $filetype, ); } /** * Returns sizes for the currently loaded image, or the image given in the $filename. * * @param string $filename The location of the file to get sizes for. * @return object An object containing width and height variables. */ abstract public function sizes($filename = null); /** * Adds a background to the image using the 'bgcolor' config option. */ abstract protected function add_background(); /** * Creates a new color usable by all drivers. * * @param string $hex The hex code of the color * @return array rgba representation of the hex and alpha values. */ protected function create_hex_color($hex) { if ($hex == null) { $red = 0; $green = 0; $blue = 0; $alpha = 0; } else { // Check if theres a # in front if (substr($hex, 0, 1) == '#') { $hex = substr($hex, 1); } // Break apart the hex if (strlen($hex) == 6 or strlen($hex) == 8) { $red = hexdec(substr($hex, 0, 2)); $green = hexdec(substr($hex, 2, 2)); $blue = hexdec(substr($hex, 4, 2)); $alpha = (strlen($hex) == 8) ? hexdec(substr($hex, 6, 2)) : 255; } else { $red = hexdec(substr($hex, 0, 1).substr($hex, 0, 1)); $green = hexdec(substr($hex, 1, 1).substr($hex, 1, 1)); $blue = hexdec(substr($hex, 2, 1).substr($hex, 2, 1)); $alpha = (strlen($hex) > 3) ? hexdec(substr($hex, 3, 1).substr($hex, 3, 1)) : 255; } } $alpha = floor($alpha / 2.55); return array( 'red' => $red, 'green' => $green, 'blue' => $blue, 'alpha' => $alpha, ); } /** * Checks if the extension is accepted by this library, and if its valid sets the $this->image_extension variable. * * @param string $filename * @param boolean $writevar Decides if the extension should be written to $this->image_extension * @param mixed $force_extension Decides if the extension should be overridden with this (or false) * @return boolean */ protected function check_extension($filename, $writevar = true, $force_extension = false) { $return = false; if ($force_extension !== false and in_array($force_extension, $this->accepted_extensions)) { return $force_extension; } foreach ($this->accepted_extensions as $ext) { if (strtolower(substr($filename, strlen($ext) * -1)) == strtolower($ext)) { $writevar and $this->image_extension = $ext; $return = $ext; } } return $return; } /** * Converts percentages, negatives, and other values to absolute integers. * * @param string $input * @param boolean $x Determines if the number relates to the x-axis or y-axis. * @return integer The converted number, usable with the image being edited. */ protected function convert_number($input, $x = null) { // Sanitize double negatives $input = str_replace('--', '', $input); // Depending on php configuration, float are sometimes converted to strings // using commas instead of points. This notation can create issues since the // conversion from string to float will return an integer. // For instance: "1.2" / 10 == 0.12 but "1,2" / 10 == 0.1... $input = str_replace(',', '.', $input); $orig = $input; $sizes = $this->sizes(); $size = $x ? $sizes->width : $sizes->height; // Convert percentages to absolutes if (substr($input, -1) == '%') { $input = floor((substr($input, 0, -1) / 100) * $size); } // Negatives are based off the bottom right if ($x !== null and $input < 0) { $input = $size + $input; } return $input; } /** * Queues a function to run at a later time. * * @param string $function The name of the function to be ran, without the leading _ */ protected function queue($function) { $func = func_get_args(); $tmpfunc = array(); for ($i = 0; $i < count($func); $i++) { $tmpfunc[$i] = var_export($func[$i], true); } $this->debug("Queued " . implode(", ", $tmpfunc) . ""); $this->queued_actions[] = $func; } /** * Runs all queued actions on the loaded image. * * @param boolean $clear Decides if the queue should be cleared once completed. */ public function run_queue($clear = null) { foreach ($this->queued_actions as $action) { $tmpfunc = array(); for ($i = 0; $i < count($action); $i++) { $tmpfunc[$i] = var_export($action[$i], true); } $this->debug('', "Executing " . implode(", ", $tmpfunc) . ""); call_user_func_array(array(&$this, '_' . $action[0]), array_slice($action, 1)); } if (($clear === null and $this->config['clear_queue']) or $clear === true) { $this->queued_actions = array(); } } /** * Reloads the image. * * @return Image_Driver */ public function reload() { $this->debug("Reloading was called!"); $this->load($this->image_fullpath); return $this; } /** * Get the file extension (type) worked out on construct * * @return string File extension */ public function extension() { return $this->image_extension; } /** * Used for debugging image output. */ protected function debug() { if ($this->config['debug']) { $messages = func_get_args(); foreach ($messages as $message) { echo '
' . $message . ' 
'; } } } }