ice framework documentation ice doc v1.10.1
Class Ice

Image

    
namespace Ice;

use Ice\Exception;

/**
 * Image manipulation support.
 *
 * @package     Ice/Image
 * @category    Component
 * @author      Ice Team
 * @copyright   (c) 2014-2023 Ice Team
 * @license     http://iceframework.org/license
 */
abstract class Image
{
    // Resizing constraints
    const NONE = 1;
    const WIDTH = 2;
    const HEIGHT = 3;
    const AUTO = 4;
    const INVERSE = 5;
    const PRECISE = 6;

    // Flipping directions
    const HORIZONTAL = 11;
    const VERTICAL = 12;

    protected static checked = false;

    protected file { get };
    protected width { get };
    protected height { get };
    protected type { get };
    protected mime { get };

    /**
     * Loads an image and prepares it for manipulation.
     *
     * 

     *  $image = new Image('upload/test.jpg');
     * 
* * @param string file Image file path * @param string driver Driver class name * * @throws Exception */ public static function factory(string file, string driver = null) { var instance; if !driver { let driver = "Ice\\Image\\Gd"; } let instance = new {driver}(file); if !(instance instanceof Image) { throw new Exception("Driver must be an Image"); } return instance; } /** * Loads information about the image. Will throw an exception if the image does not exist or is not an image. * * @param string file Image file path * * @return void * @throws Exception */ public function __construct(string file) { var file, info; try { // Get the real path to the file let file = realpath(file); // Get the image information let info = getimagesize(file); } catch Exception { // Ignore all errors while reading the image } if empty file || empty info { throw new Exception(["Not an image or invalid image: %s", file]); } // Store the image information let this->file = file, this->width = info[0], this->height = info[1], this->type = info[2], this->mime = image_type_to_mime_type(this->type); } /** * Render the current image. * *

     *  echo $image;
     * 
* * @return string Binary string, it must be rendered with the appropriate Content-Type header */ public function __toString() -> string { try { // Render the current image return this->render(); } catch Exception { // Ignore all errors } } /** * Resize the image to the given size. Either the width or the height can be omitted and the image will be resized proportionally. * *

     *  // Resize to 200 pixels on the shortest side
     *  $image->resize(200, 200);
     *
     *  // Resize to 200x200 pixels, keeping aspect ratio
     *  $image->resize(200, 200, Image::INVERSE);
     *
     *  // Resize to 500 pixel width, keeping aspect ratio
     *  $image->resize(500, null);
     *
     *  // Resize to 500 pixel height, keeping aspect ratio
     *  $image->resize(null, 500);
     *
     *  // Resize to 200x500 pixels, ignoring aspect ratio
     *  $image->resize(200, 500, Image::NONE);
     * 
* * @param integer width New width * @param integer height New height * @param integer master Master dimension * * @return Image */ public function resize(int width = null, int height = null, int master = null) -> { var ratio, tmp; if typeof master == "null" { // Choose the master dimension automatically let master = Image::AUTO; } elseif master == Image::WIDTH && width { // You should pass empty value for non-master dimension let master = Image::AUTO; // Set empty height for backward compatibility let height = null; } elseif master == Image::HEIGHT && height { let master = Image::AUTO; // Set empty width for backward compatibility let width = null; } if !width { if master === Image::NONE { // Use the current width let width = this->width; } else { // If width not set, master will be height let master = Image::HEIGHT; } } if !height { if master === Image::NONE { // Use the current height let height = this->height; } else { // If height not set, master will be width let master = Image::WIDTH; } } switch master { case Image::AUTO: // Choose direction with the greatest reduction ratio let master = (this->width / width) > (this->height / height) ? Image::WIDTH : Image::HEIGHT; break; case Image::INVERSE: // Choose direction with the minimum reduction ratio let master = (this->width / width) > (this->height / height) ? Image::HEIGHT : Image::WIDTH; break; } switch master { case Image::WIDTH: // Recalculate the height based on the width proportions let height = this->height * width / this->width; break; case Image::HEIGHT: // Recalculate the width based on the height proportions let width = this->width * height / this->height; break; case Image::PRECISE: // Resize to precise size let ratio = this->width / this->height; if width / height > ratio { let height = this->height * width / this->width; } else { let width = this->width * height / this->height; } break; } // Convert the width and height to integers, minimum value is 1px let tmp = max(round(width), 1), width = (int) tmp, tmp = max(round(height), 1), height = (int) tmp; this->doResize(width, height); return this; } /** * Crop an image to the given size. Either the width or the height can be omitted and the current width or height will be used. * * If no offset is specified, the center of the axis will be used. * If an offset of true is specified, the bottom of the axis will be used. * *

     *  // Crop the image to 200x200 pixels, from the center
     *  $image->crop(200, 200);
     * 
* * @param integer width New width * @param integer height New height * @param mixed offsetX Offset from the left * @param mixed offsetY Offset from the top * * @return Image */ public function crop(int width, int height, offsetX = null, offsetY = null) -> { if width > this->width { // Use the current width let width = this->width; } if height > this->height { // Use the current height let height = this->height; } if typeof offsetX == "null" { // Center the X offset let offsetX = round((this->width - width) / 2); } elseif offsetX === true { // Bottom the X offset let offsetX = this->width - width; } elseif offsetX < 0 { // Set the X offset from the right let offsetX = this->width - width + offsetX; } if typeof offsetY == "null" { // Center the Y offset let offsetY = round((this->height - height) / 2); } elseif offsetY === true { // Bottom the Y offset let offsetY = this->height - height; } elseif offsetY < 0 { // Set the Y offset from the bottom let offsetY = this->height - height + offsetY; } if width > (this->width - offsetX) { // Use the maximum available width let width = this->width - offsetX; } if height > (this->height - offsetY) { // Use the maximum available height let height = this->height - offsetY; } this->doCrop(width, height, offsetX, offsetY); return this; } /** * Rotate the image by a given amount. * *

     *  // Rotate 45 degrees clockwise
     *  $image->rotate(45);
     *
     *  // Rotate 90% counter-clockwise
     *  $image->rotate(-90);
     * 
* * @param integer degrees Degrees to rotate: -360-360 * * @return Image */ public function rotate(int degrees) -> { if degrees > 180 { do { // Keep subtracting full circles until the degrees have normalized let degrees -= 360; } while degrees > 180; } if degrees < -180 { do { // Keep adding full circles until the degrees have normalized let degrees += 360; } while degrees < -180; } this->doRotate(degrees); return this; } /** * Flip the image along the horizontal or vertical axis. * *

     *  // Flip the image from top to bottom
     *  $image->flip(Image::HORIZONTAL);
     *
     *  // Flip the image from left to right
     *  $image->flip(Image::VERTICAL);
     * 
* * @param integer direction Direction: Image::HORIZONTAL, Image::VERTICAL * * @return Image */ public function flip(int direction) -> { if direction !== Image::HORIZONTAL { // Flip vertically let direction = Image::VERTICAL; } this->doFlip(direction); return this; } /** * Sharpen the image by a given amount. * *

     *  // Sharpen the image by 20%
     *  $image->sharpen(20);
     * 
* * @param integer amount Amount to sharpen: 1-100 * * @return Image */ public function sharpen(int amount) -> { var tmp; // The amount must be in the range of 1 to 100 let tmp = min(max(amount, 1), 100), amount = (int) tmp; this->doSharpen(amount); return this; } /** * Add a reflection to an image. The most opaque part of the reflection will be equal to the opacity setting and fade out to full transparent. * Alpha transparency is preserved. * *

     *  // Create a 50 pixel reflection that fades from 0-100% opacity
     *  $image->reflection(50);
     *
     *  // Create a 50 pixel reflection that fades from 100-0% opacity
     *  $image->reflection(50, 100, true);
     *
     *  // Create a 50 pixel reflection that fades from 0-60% opacity
     *  $image->reflection(50, 60, true);
     * 
* * [!!] By default, the reflection will be go from transparent at the top to opaque at the bottom. * * @param integer height Reflection height * @param integer opacity Reflection opacity: 0-100 * @param boolean fadeIn True to fade in, false to fade out * * @return Image */ public function reflection(int height = null, int opacity = 100, boolean fadeIn = false) -> { var tmp; if typeof height == "null" || height > this->height { // Use the current height let height = this->height; } // The opacity must be in the range of 0 to 100 let tmp = min(max(opacity, 0), 100), opacity = (int) tmp; this->doReflection(height, opacity, fadeIn); return this; } /** * Add a watermark to an image with a specified opacity. Alpha transparency will be preserved. * * If no offset is specified, the center of the axis will be used. * If an offset of true is specified, the bottom of the axis will be used. * *

     *  // Add a watermark to the bottom right of the image
     *  $mark = new Image('upload/watermark.png');
     *  $image->watermark($mark, true, true);
     * 
* * @param Image watermark Watermark Image instance * @param integer offsetX Offset from the left * @param integer offsetY Offset from the top * @param integer opacity Opacity of watermark: 1-100 * * @return Image */ public function watermark( watermark, int offsetX = null, int offsetY = null, int opacity = 100) -> { var tmp; if typeof offsetX == "null" { // Center the X offset let tmp = round((this->width - watermark->getWidth()) / 2), offsetX = (int) tmp; } elseif offsetX === true { // Bottom the X offset let offsetX = this->width - watermark->getWidth(); } elseif offsetX < 0 { // Set the X offset from the right let offsetX = this->width - watermark->getWidth() + offsetX; } if typeof offsetY == "null" { // Center the Y offset let tmp = round((this->height - watermark->getHeight()) / 2), offsetY = (int) tmp; } elseif offsetY === true { // Bottom the Y offset let offsetY = this->height - watermark->getHeight(); } elseif offsetY < 0 { // Set the Y offset from the bottom let offsetY = this->height - watermark->getHeight() + offsetY; } // The opacity must be in the range of 1 to 100 let tmp = min(max(opacity, 1), 100), opacity = (int) tmp; this->doWatermark(watermark, offsetX, offsetY, opacity); return this; } /** * Set the background color of an image. This is only useful for images with alpha transparency. * *

     *  // Make the image background black
     *  $image->background('#000');
     *
     *  // Make the image background black with 50% opacity
     *  $image->background('#000', 50);
     * 
* * @param string color Hexadecimal color value * @param integer opacity Background opacity: 0-100 * @return Image */ public function background(string color, int opacity = 100) -> { var tmp, r, g, b; if color[0] === '#' { // Remove the hash let color = substr(color, 1); } if strlen(color) === 3 { // Convert shorthand into longhand hex notation let color = preg_replace("/./", "00", color); } // Convert the hex into RGB values let tmp = array_map("hexdec", str_split(color, 2)), r = tmp[0], g = tmp[1], b = tmp[2]; // The opacity must be in the range of 0 to 100 let tmp = min(max(opacity, 0), 100), opacity = (int) tmp; this->doBackground(r, g, b, opacity); return this; } /** * Save the image. If the filename is omitted, the original image will be overwritten. * *

     *  // Save the image as a PNG
     *  $image->save('saved/cool.png');
     *
     *  // Overwrite the original image
     *  $image->save();
     * 
* * [!!] If the file exists, but is not writable, an exception will be thrown. * [!!] If the file does not exist, and the directory is not writable, an exception will be thrown. * * @param string file New image path * @param integer quality Quality of image: 1-100 * @return boolean * @throws Exception */ public function save(string file = null, int quality = 100) -> boolean { var tmp, directory; if typeof file == "null" { // Overwrite the file let file = this->file; } if is_file(file) { if !is_writable(file) { throw new Exception(["File must be writable: %s", file]); } } else { // Get the directory of the file let directory = realpath(pathinfo(file, PATHINFO_DIRNAME)); if !is_dir(directory) || !is_writable(directory) { throw new Exception(["Directory must be writable: %s", directory]); } } // The quality must be in the range of 1 to 100 let tmp = min(max(quality, 1), 100), quality = (int) tmp; return this->doSave(file, quality); } /** * Render the image and return the binary string. * *

     *  // Render the image at 50% quality
     *  $data = image->render(null, 50);
     *
     *  // Render the image as a PNG
     *  $data = image->render('png');
     * 
* * @param string type Image type to return: png, jpg, gif, etc * @param integer quality Quality of image: 1-100 * @return string */ public function render(string type = null, int quality = 100) -> string { var tmp; if typeof type == "null" { // Use the current image type let tmp = image_type_to_extension(this->type, false), type = (string) tmp; } return this->doRender(type, quality); } /** * Execute a resize. * * @param integer width New width * @param integer height New height * * @return void */ abstract protected function doResize(int width, int height) -> void; /** * Execute a crop. * * @param integer width New width * @param integer height New height * @param integer offset_x offset From the left * @param integer offset_y offset From the top * * @return void */ abstract protected function doCrop(int width, int height, int offset_x, int offset_y) -> void; /** * Execute a rotation. * * @param integer degrees Degrees to rotate * * @return void */ abstract protected function doRotate(int degrees) -> void; /** * Execute a flip. * * @param integer direction Direction to flip * * @return void */ abstract protected function doFlip(int direction) -> void; /** * Execute a sharpen. * * @param integer amount Amount to sharpen * * @return void */ abstract protected function doSharpen(int amount) -> void; /** * Execute a reflection. * * @param integer height Reflection height * @param integer opacity Reflection opacity * @param boolean fadeIn TRUE to fade out, FALSE to fade in * * @return void */ abstract protected function doReflection(int height, int opacity, boolean fadeIn) -> void; /** * Execute a watermarking. * * @param Image image Watermarking Image * @param integer offsetX Offset from the left * @param integer offsetY Offset from the top * @param integer opacity Opacity of watermark * * @return void */ abstract protected function doWatermark( image, int offsetX, int offsetY, int opacity) -> void; /** * Execute a background. * * @param integer r Red * @param integer g Green * @param integer b Blue * @param integer opacity Opacity * * @return void */ abstract protected function doBackground(int r, int g, int b, int opacity) -> void; /** * Execute a save. * * @param string file New image filename * @param integer quality Quality * * @return boolean */ abstract protected function doSave(string file, int quality) -> boolean; /** * Execute a render. * * @param string type Image type: png, jpg, gif, etc * @param integer quality Quality * * @return string */ abstract protected function doRender(string type, int quality) -> string; }