qa-util-image.php 5.8 KB
<?php

/*
	Question2Answer (c) Gideon Greenspan

	http://www.question2answer.org/


	File: qa-include/qa-util-image.php
	Version: See define()s at top of qa-include/qa-base.php
	Description: Some useful image-related functions (using GD)


	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	More about this license: http://www.question2answer.org/license.php
*/

	if (!defined('QA_VERSION')) { // don't allow this page to be requested directly from browser
		header('Location: ../');
		exit;
	}


	function qa_has_gd_image()
/*
	Return true if PHP has the GD extension installed and it appears to be usable
*/
	{
		return extension_loaded('gd') && function_exists('imagecreatefromstring') && function_exists('imagejpeg');
	}


	function qa_image_file_too_big($imagefile, $size=null)
/*
	Check if the image in $imagefile will be too big for PHP/GD to process given memory usage and limits
	Pass the width and height limit beyond which the image will require scaling in $size (if any)
	Returns false if the image will fit fine, otherwise a safe estimate of the factor the image should be sized by
*/
	{
		if (function_exists('memory_get_usage')) {
			$gotbytes=trim(@ini_get('memory_limit'));

			switch (strtolower(substr($gotbytes, -1))) {
				case 'g':
					$gotbytes*=1024;
				case 'm':
					$gotbytes*=1024;
				case 'k':
					$gotbytes*=1024;
			}

			if ($gotbytes>0) { // otherwise we clearly don't know our limit
				$gotbytes=($gotbytes-memory_get_usage())*0.9; // safety margin of 10%

				$needbytes=filesize($imagefile); // memory to store file contents

				$imagesize=@getimagesize($imagefile);

				if (is_array($imagesize)) { // if image can't be parsed, don't worry about anything else
					$width=$imagesize[0];
					$height=$imagesize[1];
					$bits=isset($imagesize['bits']) ? $imagesize['bits'] : 8; // these elements can be missing (PHP bug) so assume this as default
					$channels=isset($imagesize['channels']) ? $imagesize['channels'] : 3; // for more info: http://gynvael.coldwind.pl/?id=223

					$needbytes+=$width*$height*$bits*$channels/8*2; // memory to load original image

					if (isset($size) && qa_image_constrain($width, $height, $size)) // memory for constrained image
						$needbytes+=$width*$height*3*2; // *2 here and above based on empirical tests
				}

				if ($needbytes>$gotbytes)
					return sqrt($gotbytes/($needbytes*1.5)); // additional 50% safety margin since JPEG quality may change
			}
		}

		return false;
	}


	function qa_image_constrain_data($imagedata, &$width, &$height, $maxwidth, $maxheight=null)
/*
	Given $imagedata containing JPEG/GIF/PNG data, constrain it proportionally to fit in $maxwidth x $maxheight.
	Return the new image data (will always be a JPEG), and set the $width and $height variables.
	If $maxheight is omitted or set to null, assume it to be the same as $maxwidth.
*/
	{
		$inimage=@imagecreatefromstring($imagedata);

		if (is_resource($inimage)) {
			$width=imagesx($inimage);
			$height=imagesy($inimage);

			// always call qa_gd_image_resize(), even if the size is the same, to take care of possible PNG transparency
			qa_image_constrain($width, $height, $maxwidth, $maxheight);
			qa_gd_image_resize($inimage, $width, $height);
		}

		if (is_resource($inimage)) {
			$imagedata=qa_gd_image_jpeg($inimage);
			imagedestroy($inimage);
			return $imagedata;
		}

		return null;
	}


	function qa_image_constrain(&$width, &$height, $maxwidth, $maxheight=null)
/*
	Given and $width and $height, return true if those need to be contrained to fit in $maxwidth x $maxheight.
	If so, also set $width and $height to the new proportionally constrained values.
	If $maxheight is omitted or set to null, assume it to be the same as $maxwidth.
*/
	{
		if (!isset($maxheight))
			$maxheight=$maxwidth;

		if (($width>$maxwidth) || ($height>$maxheight)) {
			$multiplier=min($maxwidth/$width, $maxheight/$height);
			$width=floor($width*$multiplier);
			$height=floor($height*$multiplier);

			return true;
		}

		return false;
	}


	function qa_gd_image_resize(&$image, $width, $height)
/*
	Resize the GD $image to $width and $height, setting it to null if the resize failed
*/
	{
		$oldimage=$image;
		$image=null;

		$newimage=imagecreatetruecolor($width, $height);
		$white=imagecolorallocate($newimage, 255, 255, 255); // fill with white first in case we have a transparent PNG
		imagefill($newimage, 0, 0, $white);

		if (is_resource($newimage)) {
			if (imagecopyresampled($newimage, $oldimage, 0, 0, 0, 0, $width, $height, imagesx($oldimage), imagesy($oldimage)))
				$image=$newimage;
			else
				imagedestroy($newimage);
		}

		imagedestroy($oldimage);
	}


	function qa_gd_image_jpeg($image, $output=false)
/*
	Return the JPEG data for GD $image, also echoing it to browser if $output is true
*/
	{
		ob_start();
		imagejpeg($image, null, 90);
		return $output ? ob_get_flush() : ob_get_clean();
	}


	function qa_gd_image_formats()
/*
	Return an array of strings listing the image formats that are supported
*/
	{
		$imagetypebits=imagetypes();

		$bitstrings=array(
			IMG_GIF => 'GIF',
			IMG_JPG => 'JPG',
			IMG_PNG => 'PNG',
		);

		foreach (array_keys($bitstrings) as $bit)
			if (!($imagetypebits&$bit))
				unset($bitstrings[$bit]);

		return $bitstrings;
	}


/*
	Omit PHP closing tag to help avoid accidental output
*/