<?PHP

#
#   Axis--Image.php
#   A PHP Object to Support Image File Manipulation
#
#   NOTE:  To use this object, either PHP must have internal support for the image formats
#       to be manipulated (can be checked with the imagetypes() function) or the web server
#       must be running with safe_mode off and certain external executables must be available
#       to be invoked by the server.  (A list of the needed executables can be obtained via
#       the Image::RequiredExternalExecutables() method.)
#
#   Copyright 2002-2004 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).
#
#   Author:  Edward Almasy (ealmasy@axisdata.com)
#
#   Part of the AxisPHP library v1.2.5
#   For more information see http://www.axisdata.com/AxisPHP/
#

class Image {

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

    function Image($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 internal support is available for this image type
            if ($this->ImageFormatSupportedByPhp())
            {
                if ($this->DebugLevel > 0) {  print("AI: using internal funcs for decoding image<br>\n");  }

                # 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;
                }
            }

            # if external command execution possible
            if (ini_get("safe_mode") != "1")
            {
                # determine external save command to convert image to portable format
                switch ($this->Type())
                {
                    case IMGTYPE_BMP:
                        $this->DecodeCommand = "bmptoppm ";
                        break;

                    case IMGTYPE_GIF:
                        $this->DecodeCommand = "giftopnm ";
                        break;

                    case IMGTYPE_PNG:
                        $this->DecodeCommand = "pngtopnm ";
                        break;

                    case IMGTYPE_JPEG:
                        $this->DecodeCommand = "djpeg ";
                        break;

                    default:
                        $this->ErrorStatus = AI_UNKNOWNTYPE;
                        break;
                }
                $this->DecodeCommand .= realpath($this->SourceFileName)." ";
            }
            else
            {
                # if format wasn't supported internally
                if (!$this->ImageFormatSupportedByPhp())
                {
                    # 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 by internal functions
            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);
                        break;

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

                    default:
                        $this->ErrorStatus = AI_INTERNALERROR;
                        break;
                }
            }
            else
            {
                # build command (convert image to intermediate form)
                $Command = $this->DecodeCommand;

                # build command (crop if requested)
                if (isset($this->CroppedXSize))
                {
                    $Command .= "| pnmcut ".$this->CroppedXOrigin." ".$this->CroppedYOrigin." "
                            .$this->CroppedXSize." ".$this->CroppedYSize." ";
                }

                # build command (scale if requested)
                if (isset($this->ScaledXSize) || isset($this->ScaledYSize))
                {
                    $Command .= "| pnmscale ";
                    if ($this->MaintainAspectRatio
                        && isset($this->ScaledXSize) && isset($this->ScaledYSize))
                    {
                        $Command .= "-xysize ".$this->ScaledXSize." ".$this->ScaledYSize;
                    }
                    else
                    {
                        if (isset($this->ScaledXSize)) {  $Command .= "-xsize ".$this->ScaledXSize." ";  }
                        if (isset($this->ScaledYSize)) {  $Command .= "-ysize ".$this->ScaledYSize." ";  }
                    }
                }

                # build command (convert to final form)
                switch ($NewImageType)
                {
                    case IMGTYPE_BMP:
                        $Command .= "| ppmquant 256 | ppmtobmp -windows ";
                        break;

                    case IMGTYPE_GIF:
                        $Command .= "| ppmquant 256 | ppmtogif ";
                        break;

                    case IMGTYPE_PNG:
                        $Command .= "| ppmquant 256 | pnmtopng ";
                        break;

                    case IMGTYPE_JPEG:
                    default:
                        $Command .= "| cjpeg -quality ".$this->JpegSaveQuality." ";
                        break;
                }

                # build command (send output to new image file)
                $Command .= "> ".$FileName;

                # execute command

                $CommandResult = system($Command);

                # set error status if command failed
                if ($CommandResult === FALSE)
                {
                    $this->ErrorStatus = AI_PPMCMDFAILED;
                    $this->FailedCommand = $Command;
                }
            }
        }

        # 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;
    }

    # return the image type
    function Type($FileName = NULL)
    {
        if ($FileName == NULL) {  $FileName = $this->SourceFileName;  }
        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;  }
        else {  return IMGTYPE_UNKNOWN;  }
    }

    # 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;
    }

    # check availability of external executables and return list of any that are not found
    function MissingExternalExecutables()
    {
        # start with empty list of missing executables
        $MissingExecutables = array();

        # for each required executable
        foreach (Image::RequiredExternalExecutables() as $Executable)
        {
            # if executable did not appear to be available
            if (Image::ExternalExecutableIsAvailable($Executable) == FALSE)
            {
                # add executable to list of missing
                $MissingExecutables[] = $Executable;
            }
        }

        # return list of missing executables to caller
        return $MissingExecutables;
    }

    # return list of all required external executables
    function RequiredExternalExecutables($ImageTypes = NULL)
    {
        # start with the assumption that no executables are required
        $RequiredExecutables = array();

        # if no image types specified assume all image types
        if ($ImageTypes == NULL)
        {
            $ImageTypes = IMGTYPE_JPEG | IMGTYPE_GIF | IMGTYPE_BMP | IMGTYPE_PNG;
        }

        # add format-specific executables that may or may not be needed
        if (($ImageTypes & IMGTYPE_JPEG)
            && (!function_exists("imagetypes") || !(imagetypes() & IMG_JPG)))
        {
            $RequiredExecutables[] = "djpeg";
            $RequiredExecutables[] = "cjpeg";
        }
        if (($ImageTypes & IMGTYPE_GIF)
            && (!function_exists("imagetypes") || !(imagetypes() & IMG_GIF)))
        {
            $RequiredExecutables[] = "giftopnm";
            $RequiredExecutables[] = "ppmtogif";
        }
        if (($ImageTypes & IMGTYPE_PNG)
            && (!function_exists("imagetypes") || !(imagetypes() & IMG_PNG)))
        {
            $RequiredExecutables[] = "pngtopnm";
            $RequiredExecutables[] = "pnmtopng";
        }
        if ($ImageTypes & IMGTYPE_BMP)
        {
            $RequiredExecutables[] = "bmptoppm";
            $RequiredExecutables[] = "ppmtobmp";
        }

        # if any format-specific executables needed
        if (count($RequiredExecutables) != 0)
        {
            # add basic manipulation executables
            $RequiredExecutables[] = "pnmcut";
            $RequiredExecutables[] = "pnmscale";
            $RequiredExecutables[] = "ppmquant";
            $RequiredExecutables[] = "pnmfile";
        }

        # return list of required executables to caller
        return $RequiredExecutables;
    }

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

        # if JPEG is supported by PHP or needed external executables are available
        if ((function_exists("imagetypes") && defined("IMG_JPG")
                        && (imagetypes() & IMG_JPG))
                || (Image::ExternalExecutableIsAvailable("djpeg")
                && Image::ExternalExecutableIsAvailable("cjpeg")))
        {
            # add JPEG to list of supported formats
            $Supported |= IMGTYPE_JPEG;
        }

        # if GIF is supported by PHP or needed external executables are available
        if ((function_exists("imagetypes") && defined("IMG_GIF")
                        && (imagetypes() & IMG_GIF))
                || (Image::ExternalExecutableIsAvailable("giftopnm")
                && Image::ExternalExecutableIsAvailable("ppmtogif")))
        {
            # add GIF to list of supported formats
            $Supported |= IMGTYPE_GIF;
        }

        # if PNG is supported by PHP or needed external executables are available
        if ((function_exists("imagetypes") && defined("IMG_PNG")
                        && (imagetypes() & IMG_PNG))
                || (Image::ExternalExecutableIsAvailable("pngtopnm")
                && Image::ExternalExecutableIsAvailable("pnmtopng")))
        {
            # add PNG to list of supported formats
            $Supported |= IMGTYPE_PNG;
        }

        # if needed external executables are available for BMP
        # needed executables being present is not sufficient for BMP support in PHP
        # test is being shortcutted to false to reflect no PHP support for BMPs
        if (0 && Image::ExternalExecutableIsAvailable("bmptoppm")
                && Image::ExternalExecutableIsAvailable("ppmtobmp"))
        {
            # add BMP to list of supported formats
            $Supported |= IMGTYPE_BMP;
        }

        # 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))
        {
            # if we are using internal image functions
            if ($this->ImageFormatSupportedByPhp())
            {
                # read size information from image object
                $this->ImageXSize = imagesx($this->ImageObj);
                $this->ImageYSize = imagesy($this->ImageObj);
            }
            else
            {
                # retrieve image info string
                $Command = $this->DecodeCommand."| pnmfile ";
                $Result = exec("$Command");

                # parse image info string
                $Pieces = preg_split("/\s+/", $Result);
                $this->ImageXSize = $Pieces[3];
                $this->ImageYSize = $Pieces[5];
            }
        }
    }

    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;
        }
    }

    # check whether external executable is available and report result back to caller
    function ExternalExecutableIsAvailable($ExecutableName)
    {
        static $ExecutableAvailable;

        if (!isset($ExecutableAvailable[$ExecutableName]))
        {
            $Result = exec("which ".$ExecutableName." 2>&1");
            $ExecutableAvailable[$ExecutableName] =
                (basename($Result) == $ExecutableName) ? TRUE : FALSE;
        }
        return $ExecutableAvailable[$ExecutableName];
    }
}

# 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;  }
}

?>
