/**
* Creates a thumbnail of a PNG/JPEG/GIF file. PNG-24 alpha channels are supported, GIF transparency however is not.
*
* Use $_GET['image'] to determine the source file
*
* HTTP Content-Type will be the same as the source file or text/plain if an error occurs
*
* @author David Luposchainsky
*/
class Thumbnail {
/**
* Defines the maximum dimension of the resulting thumbnail
*
* @var int
*/
protected $MaxResultDimension;
/**
* If set to TRUE the aspect ratio of the source image will be maintained (by scaling one down to maximum height/width and calculating the other one appropriately), else the image will always be stretched to maximum dimensions
*
* @var bool
*/
protected $MaintainAspectRatio;
/**
* If set to TRUE, images smaller than the maximum width will
* not be resized (i.e. enlarged) at all
*
* @var bool
*/
protected $EnlargeSmallImages;
/**
* Settings for the checker background for transparent areas of
* resized PNG graphics. If set to FALSE, the transparency will
* be maintained, otherwise the array below defines the checker
* pattern appearance.
*
* @var false|array
*/
protected $TransparencyChecker;
/**
* Stores the filename of the source image
*
* @var string Path to the source image
*/
protected $SourceFilename;
/**
* Stores the MIME type of the image generated by self::generate(). Until that function is run, the value is undefined.
*
* @var string The MIME type of the generated thumbnail
*/
protected $Mime;
/**
* Initializes default data
*
* @param string $SourceFilename Path to the source image
* @throws Exception
*/
function __construct($SourceFilename) {
// Check whether GD2 is ready to work
if(!function_exists('getimagesize') || !function_exists('imagecreatetruecolor')) {
throw new Exception('GD2 not installed/configured');
}
$this->SourceFilename = $SourceFilename;
// Initialize default values
$this->MaxResultDimension = array(256, 192);
$this->MaintainAspectRatio = true;
$this->EnlargeSmallImages = false;
$this->TransparencyChecker = array(
'Width' => 8, // Width of a checker cell
'Height' => 8, // Height of a checker cell
'R1' => 0xe8, // Red of cell type #1 (particularly the top-left one)
'G1' => 0xe8, // Green
'B1' => 0xe8, // Blue
'R2' => 0xff, // Red of cell type #2
'G2' => 0xff, // Green
'B2' => 0xff, // Blue
);
// Check whether the source file exists
if(!file_exists($this->SourceFilename)) {
throw new Exception('File not found');
}
}
function setMaxDimension($MaxWidth, $MaxHeight) {
$this->MaxResultDimension = array($MaxWidth, $MaxHeight);
}
function setMaintainAspectRatio($Value) {
$this->MaintainAspectRatio = $Value;
}
function setEnlargeSmallImages($Value) {
$this->EnlargeSmallImages = $Value;
}
function setTransparencyChecker($Settings) {
if(!$Settings) {
$this->TransparencyChecker = false;
} else {
foreach($Settings as $Key => $Value) {
$this->TransparencyChecker[$Key] = $Value;
}
}
}
function getMime() {
if(is_string($this->Mime)) {
return $this->Mime;
} else {
throw new Exception('No valid MIME type available, make sure to run generate(true) first');
}
}
/**
* Generates the thumbnail
*
* @param bool $Return If set to TRUE the image code is returned instead of printed.
* @return string Returns the thumbnail data
* @throws Exception
*/
function generate($Return = false) {
// Determine file dimensions and MIME of the source image
$ImageSize = getimagesize($this->SourceFilename);
if(!$ImageSize) {
throw new Exception('imagesize() error');
}
$this->Mime = $ImageSize['mime'];
$SourceImageWidth = $ImageSize[0];
$SourceImageHeight = $ImageSize[1];
// Create source image ressource
switch($this->Mime) {
case 'image/jpeg':
$SourceImage = imagecreatefromjpeg($this->SourceFilename);
break;
case 'image/png':
$SourceImage = imagecreatefrompng($this->SourceFilename);
break;
case 'image/gif':
$SourceImage = imagecreatefromgif($this->SourceFilename);
break;
default:
throw new Exception('Only the following MIME types are allowed: image/png, image/jpeg, image/gif');
}
// :TODO: compress the block below
// Determine thumbnail image size
if($this->MaintainAspectRatio) {
$NewImageWidth = $this->MaxResultDimension[0];
$NewImageHeight = round($NewImageWidth / $SourceImageWidth * $SourceImageHeight);
if($NewImageHeight > $this->MaxResultDimension[1]) { // If the x-scaled image should exceed the maximum y size scale down the y axis instead of the x
$NewImageHeight = $this->MaxResultDimension[1];
$NewImageWidth = round($NewImageHeight / $SourceImageHeight * $SourceImageWidth);
}
} else {
$NewImageWidth = $MaxResultDimension[0];
$NewImageHeight = $MaxResultDimension[1];
}
if(!$this->EnlargeSmallImages && ($NewImageWidth > $SourceImageWidth || $NewImageHeight > $SourceImageHeight)) {
$NewImageWidth = $SourceImageWidth;
$NewImageHeight = $SourceImageHeight;
}
// Generate thumbnail image
$NewImage = imagecreatetruecolor($NewImageWidth, $NewImageHeight);
switch($this->Mime) { // Handle output differently for each format
case 'image/png': // Handle transparent PNG areas
if($this->TransparencyChecker) {
// Fill background with color #1
$CheckerColor1 = imagecolorallocate($NewImage, $this->TransparencyChecker['R1'], $this->TransparencyChecker['G1'], $this->TransparencyChecker['B1']);
imagefilledrectangle($NewImage, 0, 0, $this->MaxResultDimension[0] - 1, $this->MaxResultDimension[1] - 1, $CheckerColor1);
// Initialize color #2
$CheckerColor2 = imagecolorallocate($NewImage, $this->TransparencyChecker['R2'], $this->TransparencyChecker['G2'], $this->TransparencyChecker['B2']);
// Paint the pattern
$XLineOffset = 0; // Offset to create checker pattern (move every 2nd line to the right)
for($y = 0; $y < $NewImageHeight; $y += $this->TransparencyChecker['Height']) {
for($x = 0; $x < $NewImageWidth; $x += (2 * $this->TransparencyChecker['Width'])) {
$x1 = $x + $XLineOffset;
$y1 = $y;
$x2 = $x1 + $this->TransparencyChecker['Width'] - 1;
$y2 = $y1 + $this->TransparencyChecker['Height'] - 1;
imagefilledrectangle($NewImage, $x1, $y1, $x2, $y2, $CheckerColor2);
}
if($XLineOffset == 0) {
$XLineOffset = $this->TransparencyChecker['Width'];
} else {
$XLineOffset = 0;
}
}
} else {
imagealphablending($NewImage, false); // Don't ask me why these 2 parameters are set to true-false, I just checked all 3 other combinations when true-true didn't work :\
imagesavealpha($NewImage, true);
}
break;
case 'image/jpeg': // Interlace JPEGs
imageinterlace($NewImage, 1);
break;
}
// Scale the image down
imagecopyresampled($NewImage, $SourceImage, 0, 0, 0, 0, $NewImageWidth, $NewImageHeight, $SourceImageWidth, $SourceImageHeight);
// Generate image
if($Return) {
ob_start();
} else {
header('content-type: '.$this->Mime);
}
switch($this->Mime) {
case 'image/jpeg':
imagejpeg($NewImage, null, 100);
break;
case 'image/png':
imagepng($NewImage);
break;
case 'image/gif':
imagegif($NewImage);
break;
// default has already been caught in the switch before
}
imagedestroy($SourceImage);
imagedestroy($NewImage);
if($Return) {
$ImageData = ob_get_contents();
ob_end_clean();
return $ImageData;
}
}
}