<?PHP
#
#   Axis--Image.php
#   A PHP Object to Support Image File Manipulation
#
#   Copyright 1999-2013 Axis Data
#   This code is free software that can be used or redistributed under the
#   terms of Version 2 of the GNU General Public License, as published by the
#   Free Software Foundation (http://www.fsf.org).
#
#   Part of the AxisPHP library v1.2.5
#   For more information see http://www.axisdata.com/AxisPHP/
#

class Image {

    # ---- PUBLIC INTERFACE --------------------------------------------------

    function __construct($SourceFileName, $DebugLevel = 0)
    {
        # set debug level
        $this->DebugLevel = $DebugLevel;

        # save source file name
        $this->SourceFileName = $SourceFileName;

        # set default values
        $this->JpegSaveQuality = 80;
        $this->ErrorStatus = AI_OKAY;
        $this->FailedCommand = "";

        # get GD library version
        if (extension_loaded("gd"))
        {
            if (in_array("imagecreatetruecolor", get_extension_funcs("gd")))
            {
                $this->GDVersion = 2;
            }
            else
            {
                $this->GDVersion = 1;
            }
        }
        else
        {
            $this->GDVersion = 0;
        }

        # if source file is readable
        if (is_readable(realpath($SourceFileName)))
        {
            # if support is available for this image type
            if ($this->ImageFormatSupportedByPhp())
            {
                # create PHP image object
                switch ($this->Type())
                {
                    case IMGTYPE_JPEG:
                        if ($this->DebugLevel > 1) {  print("AI: file format is JPEG<br>\n");  }
                        $this->ImageObj = imagecreatefromjpeg($this->SourceFileName);
                        break;

                    case IMGTYPE_GIF:
                        if ($this->DebugLevel > 1) {  print("AI: file format is GIF<br>\n");  }
                        $this->ImageObj = imagecreatefromgif($this->SourceFileName);
                        break;

                    case IMGTYPE_BMP:
                        if ($this->DebugLevel > 1) {  print("AI: file format is BMP<br>\n");  }
                        $this->ImageObj = imagecreatefrombmp($this->SourceFileName);
                        break;

                    case IMGTYPE_PNG:
                        if ($this->DebugLevel > 1) { print("AI: file format is PNG<br>\n"); }
                        $this->ImageObj = imagecreatefrompng($this->SourceFileName);
                        break;

                    default:
                        $this->ErrorStatus = AI_INTERNALERROR;
                        break;
                }

                # if PHP image object creation failed
                if (FALSE === $this->ImageObj)
                {
                    # set error status
                    $this->ErrorStatus = AI_IMGOBJCREATEFAILED;
                }
            }
            else
            {
                # set error status to indicate unsupported image format
                $this->ErrorStatus = AI_UNSUPPORTEDFORMAT;
            }
        }
        else
        {
            # set error status
            $this->ErrorStatus = AI_FILEUNREADABLE;
        }
    }

    # save image with a new name and (optionally) a new type
    function SaveAs($FileName, $NewImageType = NULL)
    {
        # assume we will succeed
        $this->ErrorStatus = AI_OKAY;

        # if destination file exists and is not writable
        if (file_exists($FileName) && (is_writable($FileName) != TRUE))
        {
            # set error code
            $this->ErrorStatus = AI_DESTINATIONUNWRITABLE;
        }
        # else if destination directory is not writable
        elseif (is_writable(dirname($FileName)) != TRUE)
        {
            # set error code
            $this->ErrorStatus = AI_DESTINATIONUNWRITABLE;
        }
        else
        {
            # if no image type specified try to determine based on file name or use source file type
            if ($NewImageType == NULL)
            {
                if ($this->Type($FileName) != IMGTYPE_UNKNOWN)
                    {  $NewImageType = $this->Type($FileName);  }
                else
                    {  $NewImageType = $this->Type();  }
            }

            # if input and output types both supported
            if ($this->ImageFormatSupportedByPhp() && $this->ImageFormatSupportedByPhp($NewImageType))
            {
                # if image cropping or scaling was requested
                if (isset($this->CroppedXSize)
                    || isset($this->ScaledXSize)
                    || isset($this->ScaledYSize))
                {
                    # determine destination image size
                    if (isset($this->ScaledXSize) && isset($this->ScaledYSize)
                            && ($this->MaintainAspectRatio != TRUE))
                    {
                        $DstXSize = $this->ScaledXSize;
                        $DstYSize = $this->ScaledYSize;
                    }
                    elseif (isset($this->ScaledXSize)
                            || ($this->ScaledXSize > $this->ScaledYSize))
                    {
                        $DstXSize = $this->ScaledXSize;
                        $DstYSize = ($this->ScaledXSize * $this->YSize())
                                / $this->XSize();
                    }
                    elseif (isset($this->ScaledYSize))
                    {
                        $DstXSize = ($this->ScaledYSize * $this->XSize())
                                / $this->YSize();
                        $DstYSize = $this->ScaledYSize;
                    }
                    elseif (isset($this->CroppedXSize))
                    {
                        $DstXSize = $this->CroppedXSize;
                        $DstYSize = $this->CroppedYSize;
                    }
                    else
                    {
                        $DstXSize = $this->XSize();
                        $DstYSize = $this->YSize();
                    }

                    # create destination image object
                    if (($NewImageType == IMGTYPE_GIF) || ($this->GDVersion < 2))
                    {
                        $DstImage = imagecreate($DstXSize, $DstYSize);
                    }
                    else
                    {
                        $DstImage = imagecreatetruecolor($DstXSize, $DstYSize);
                    }

                    # determine area of source image to use
                    if (isset($this->CroppedXSize))
                    {
                        $SrcXSize = $this->CroppedXSize;
                        $SrcYSize = $this->CroppedYSize;
                    }
                    else
                    {
                        $SrcXSize = $this->XSize();
                        $SrcYSize = $this->YSize();
                    }

                    # copy/scale portion of original image to destination image
                    if ($this->GDVersion >= 2)
                    {
                        imagecopyresampled($DstImage, $this->ImageObj,
                                           0, 0,
                                           $this->CroppedXOrigin, $this->CroppedYOrigin,
                                           $DstXSize, $DstYSize,
                                           $SrcXSize, $SrcYSize);
                    }
                    else
                    {
                        imagecopyresized($DstImage, $this->ImageObj,
                                           0, 0,
                                           $this->CroppedXOrigin, $this->CroppedYOrigin,
                                           $DstXSize, $DstYSize,
                                           $SrcXSize, $SrcYSize);
                    }
                }
                else
                {
                    $DstImage =& $this->ImageObj;
                }

                # save image to new file
                switch ($NewImageType)
                {
                    case IMGTYPE_GIF:
                        imagegif($DstImage, $FileName);
                        break;

                    case IMGTYPE_JPEG:
                        imagejpeg($DstImage, $FileName, $this->JpegSaveQuality);
                        break;

                    case IMGTYPE_PNG:
                        imagepng($DstImage, $FileName, 9);
                        break;

                    case IMGTYPE_BMP:
                        imagewbmp($DstImage, $FileName);

                    default:
                        $this->ErrorStatus = AI_INTERNALERROR;
                        break;
                }
            }
            else
            {
                # set error status to indicate unsupported image format
                $this->ErrorStatus = AI_UNSUPPORTEDFORMAT;
            }
        }

        # report success or failure to caller
        return $this->ErrorStatus;
    }

    # return the X (horizontal) image size in pixels
    function XSize()
    {
        $this->ReadSize();
        return $this->ImageXSize;
    }

    # return the Y (vertical) image size in pixels
    function YSize()
    {
        $this->ReadSize();
        return $this->ImageYSize;
    }

    # specify the size to scale the image to for the next SaveAs()
    function ScaleTo($ScaledXSize, $ScaledYSize, $MaintainAspectRatio = FALSE)
    {
        # save size for scaling
        $this->ScaledXSize = $ScaledXSize;
        $this->ScaledYSize = $ScaledYSize;
        $this->MaintainAspectRatio = $MaintainAspectRatio;
    }

    # specify the size to crop the image to for the next SaveAs()
    function CropTo($CroppedXSize, $CroppedYSize, $CroppedXOrigin = 0, $CroppedYOrigin = 0)
    {
        # save origin and size for cropping
        $this->CroppedXSize = $CroppedXSize;
        $this->CroppedYSize = $CroppedYSize;
        $this->CroppedXOrigin = $CroppedXOrigin;
        $this->CroppedYOrigin = $CroppedYOrigin;
    }

    /**
    * Get the image type.  If image file does not exist, the file name is
    * examined to try to determine the image type.
    * @param string $FileName Full name (with path) of image file.  (OPTIONAL,
    *       defaults to previously-stored file name for this object)
    * @return enum Image type.
    */
    function Type($FileName = NULL)
    {
        if ($FileName == NULL) {  $FileName = $this->SourceFileName;  }
        if (is_readable($FileName))
        {
            switch (exif_imagetype($FileName))
            {
                case IMAGETYPE_GIF:  return IMGTYPE_GIF;
                case IMAGETYPE_JPEG: return IMGTYPE_JPEG;
                case IMAGETYPE_PNG:  return IMGTYPE_PNG;
                case IMAGETYPE_BMP:  return IMGTYPE_BMP;
            }
        }
        if (preg_match("/.*\\.jp[e]{0,1}g$/i", $FileName))
            {  return IMGTYPE_JPEG;  }
        elseif (preg_match("/.*\\.gif$/i", $FileName))
            {  return IMGTYPE_GIF;  }
        elseif (preg_match("/.*\\.bmp$/i", $FileName))
            {  return IMGTYPE_BMP;  }
        elseif (preg_match("/.*\\.png$/i", $FileName))
            {  return IMGTYPE_PNG;  }
        return IMGTYPE_UNKNOWN;
    }

    /**
    * Get the MIME type for the image.
    * @return Returns the MIME type for the image.
    */
    public function Mimetype()
    {
        switch ($this->Type())
        {
            # if the image type is known
            case IMGTYPE_JPEG: return "image/jpeg";
            case IMGTYPE_PNG: return "image/png";
            case IMGTYPE_GIF: return "image/gif";
            case IMGTYPE_BMP: return "image/bmp";

            # the image type isn't known
            default:
                $Mimetype = NULL;
                $FilePath = $this->SourceFileName;

                # PHP >= 5.3
                if (function_exists("finfo_open"))
                {
                    # construct a handle to get mimetype info
                    $FInfoHandle = finfo_open(FILEINFO_MIME);

                    # if the handle is okay
                    if ($FInfoHandle)
                    {
                        # get the mimetype info for the file
                        $FInfoMime = finfo_file($FInfoHandle, $FilePath);

                        # close the handle
                        finfo_close($FInfoHandle);

                        # if the mimetype info fetch was successful
                        if ($FInfoMime)
                        {
                            $Mimetype = $FInfoMime;
                        }
                    }
                }

                # PHP < 5.3
                else if (function_exists("mime_content_type"))
                {
                    # mime_content_type has been deprecated, but it may be
                    # the only way to get the mimetype for PHP < 5.3
                    $MimeType = mime_content_type($FilePath);

                    # if the mimetype info fetch was successful
                    if ($MimeType)
                    {
                        $Mimetype = $MimeType;
                    }
                }

                return $Mimetype;
        }
    }

    # return the file name extension for the image
    static function Extension($Type = NULL)
    {
        if ($Type === NULL)
        {
            return Image::$AxisImageFileExtensions[$this->Type()];
        }
        else
        {
            if (isset(Image::$AxisImageFileExtensions[$Type]))
            {
                return Image::$AxisImageFileExtensions[$Type];
            }
            else
            {
                return NULL;
            }
        }
    }

    # set/get the quality (0-100) for JPEG images created with SaveAs()
    function JpegQuality($NewSetting = NULL)
    {
        if ($NewSetting != NULL) {  $this->JpegSaveQuality = $NewSetting;  }
        return $this->JpegSaveQuality;
    }

    # return supported image formats
    static function SupportedFormats()
    {
        # start out assuming no formats are supported
        $Supported = 0;

        # if JPEG is supported by PHP
        if (function_exists("imagetypes") && defined("IMG_JPG")
                        && (imagetypes() & IMG_JPG))
        {
            # add JPEG to list of supported formats
            $Supported |= IMGTYPE_JPEG;
        }

        # if GIF is supported by PHP
        if (function_exists("imagetypes") && defined("IMG_GIF")
                        && (imagetypes() & IMG_GIF))
        {
            # add GIF to list of supported formats
            $Supported |= IMGTYPE_GIF;
        }

        # if PNG is supported by PHP
        if (function_exists("imagetypes") && defined("IMG_PNG")
                        && (imagetypes() & IMG_PNG))
        {
            # add PNG to list of supported formats
            $Supported |= IMGTYPE_PNG;
        }

        # report to caller what formats are supported
        return $Supported;
    }

    # return names (upper-case extensions) of supported image formats
    static function SupportedFormatNames()
    {
        # assume that no formats are supported
        $FormatNames = array();

        # retrieve supported formats
        $SupportedFormats = Image::SupportedFormats();

        # for each possible supported format
        foreach (Image::$AxisImageFileExtensions as $ImageType => $ImageExtension)
        {
            # if format is supported
            if ($ImageType & $SupportedFormats)
            {
                # add format extension to list of supported image format names
                $FormatNames[] = strtoupper($ImageExtension);
            }
        }

        # return supported image format names to caller
        return $FormatNames;
    }

    # return the error status set by the constructor or the last call to SaveAs()
    function Status()
    {
        return $this->ErrorStatus;
    }

    # return string containing external command that failed
    function FailedExternalCommand()
    {
        return $this->FailedCommand;
    }


    # ---- PRIVATE INTERFACE -------------------------------------------------

    var $GDVersion;
    var $ImageObj;
    var $SourceFileName;
    var $ImageXSize;
    var $ImageYSize;
    var $ScaledXSize;
    var $ScaledYSize;
    var $MaintainAspectRatio;
    var $CroppedXSize;
    var $CroppedYSize;
    var $CroppedXOrigin;
    var $CroppedYOrigin;
    var $JpegSaveQuality;
    var $DecodeCommand;
    var $ErrorStatus;
    var $FailedCommand;
    var $DebugLevel;

    # image file extensions
    private static $AxisImageFileExtensions = array(
            IMGTYPE_JPEG => "jpg",
            IMGTYPE_GIF  => "gif",
            IMGTYPE_BMP  => "bmp",
            IMGTYPE_PNG  => "png",
            );

    function ReadSize()
    {
        # if we do not already have image info
        if (!isset($this->ImageXSize))
        {
            # read size information from image object
            $this->ImageXSize = imagesx($this->ImageObj);
            $this->ImageYSize = imagesy($this->ImageObj);
        }
    }

    function ImageFormatSupportedByPhp($Format = NULL)
    {
        if ($Format == NULL) {  $Format = $this->Type();  }

        if (!function_exists("imagetypes")) {  return FALSE;  }

        switch ($Format)
        {
            case IMGTYPE_JPEG:
                return (imagetypes() & IMG_JPG) ? TRUE : FALSE;
                break;

            case IMGTYPE_GIF:
                return (imagetypes() & IMG_GIF) ? TRUE : FALSE;
                break;

            case IMGTYPE_BMP:
                return FALSE;
                break;

            case IMGTYPE_PNG:
                return (imagetypes() & IMG_PNG) ? TRUE : FALSE;
                break;

            default:
                return FALSE;
                break;
        }
    }
}

# image type definitions (these are purposefully different from those defined by PHP GD lib)
define("IMGTYPE_UNKNOWN", 0);
define("IMGTYPE_JPEG",    1);
define("IMGTYPE_GIF",     2);
define("IMGTYPE_BMP",     4);
define("IMGTYPE_PNG",     8);

# error status definitions
define("AI_OKAY",                   0);
define("AI_FILEUNREADABLE",         1);
define("AI_IMGOBJCREATEFAILED",     2);
define("AI_PPMCMDFAILED",           4);
define("AI_INTERNALERROR",          8);
define("AI_UNKNOWNTYPE",           16);
define("AI_UNSUPPORTEDFORMAT",     32);
define("AI_DESTINATIONUNWRITABLE", 64);

# supply imagetypes() function if not defined
if (!function_exists("imagetypes"))
{
    # (returning 0 indicates no image types supported)
    function imagetypes() {  return 0;  }
}

