<?php
/*
 * $RCSfile: SquareThumbToolkit.class,v $
 *
 * Gallery - a web based photo album viewer and editor
 * Copyright (C) 2000-2005 Bharat Mediratta
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA  02110-1301, USA.
 */
/**
 * @version $Revision: 1.13 $ $Date: 2005/08/23 03:49:55 $
 * @package SquareThumb
 * @author Alan Harder <alan.harder@sun.com>
 */

/**
 * Load required classes
 */
GalleryCoreApi::relativeRequireOnce('modules/core/classes/GalleryToolkit.class');

/**
 * A version of GalleryToolkit to enforce creation of square thumbnails
 *
 * @package SquareThumb
 * @subpackage Classes
 */
class SquareThumbToolkit extends GalleryToolkit {

    /**
     * @see GalleryToolkit::getProperty()
     */
    function getProperty($mimeType, $propertyName, $sourceFilename) {
	return array(GalleryStatus::error(ERROR_UNIMPLEMENTED, __FILE__, __LINE__), null);
    }

    /**
     * @see GalleryToolkit::performOperation()
     */
    function performOperation($mimeType, $operationName, $sourceFilename,
			      $destFilename, $parameters, $context=array()) {
	global $gallery;
	$platform = $gallery->getPlatform();

	if ($operationName != 'thumbnail') {
	    return array(GalleryStatus::error(ERROR_UNSUPPORTED_OPERATION, __FILE__, __LINE__,
					      "$operationName $mimeType"), null, null);
	}
	list ($ret, $square) = GalleryCoreApi::fetchAllPluginParameters('module', 'squarethumb');
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null, null);
	}

	/* Get dimensions */
	if (isset($context['width'])) {
	    $width = $context['width'];
	    $height = $context['height'];
	} else {
	    list ($ret, $toolkit) = GalleryCoreApi::getToolkitByProperty($mimeType, 'dimensions');
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null, null);
	    }
	    if (!isset($toolkit)) {
		return array(GalleryStatus::error(ERROR_UNSUPPORTED_OPERATION, __FILE__, __LINE__,
						  "dimensions $mimeType"), null, null);
	    }
	    list ($ret, $dimensions) =
		$toolkit->getProperty($mimeType, 'dimensions', $sourceFilename);
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null, null);
	    }
	    list ($width, $height) = $dimensions;
	}
	$subContext = array('width' => $width, 'height' => $height);

	/* Try to crop/fit according to param, but fallback to other based on toolkit support */
	if ($width != $height) {
	    for ($i = 0; $i < 2; $i++) {
		$operation = ($square['mode'] == 'fit') ? 'composite' : 'crop';
		list ($ret, $toolkit, $nextMime) =
		    GalleryCoreApi::getToolkitByOperation($mimeType, $operation);
		if ($ret->isError()) {
		    return array($ret->wrap(__FILE__, __LINE__), null, null);
		}
		if (isset($toolkit)) {
		    break;
		}
		$square['mode'] = ($square['mode'] == 'fit') ? 'crop' : 'fit';
	    }
	}

	if ($width != $height && $square['mode'] == 'crop') {
	    /* Crop to square */
	    if (!isset($toolkit)) {
		return array(GalleryStatus::error(ERROR_UNSUPPORTED_OPERATION, __FILE__, __LINE__,
						  "crop $mimeType"), null, null);
	    }
	    if ($width > $parameters[0] && $height > $parameters[0]) {
		list ($ret, $nextToolkit) = $this->_getThumbnailToolkit($nextMime);
		if ($ret->isSuccess()) {
		    $subContext['next.toolkit'] = $nextToolkit;
		    $subContext['next.operation'] = 'thumbnail';
		}
	    }

	    $args = ($width > $height)
		  ? array( 100*(1-$height/$width)/2, 0, 100*$height/$width, 100 )
		  : array( 0, 100*(1-$width/$height)/2, 100, 100*$width/$height );
	    list ($ret, $mimeType, $subContext) = $toolkit->performOperation(
		$mimeType, 'crop', $sourceFilename, $destFilename, $args, $subContext);
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null, null);
	    }
	    $sourceFilename = $destFilename;
	}

	if ($subContext['width'] > $parameters[0] || $subContext['height'] > $parameters[0]) {
	    /* Scale to thumb size */
	    list ($ret, $toolkit) = $this->_getThumbnailToolkit($mimeType);
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null, null);
	    }
	    $subContext['next.toolkit'] = $subContext['next.operation'] = null;
	    list ($ret, $mimeType, $subContext) =
		$toolkit->performOperation($mimeType, 'thumbnail', $sourceFilename, $destFilename,
					   $parameters, $subContext);
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null, null);
	    }
	    $sourceFilename = $destFilename;
	}

	if ($subContext['width'] != $subContext['height'] && $square['mode'] == 'fit') {
	    /* Fit to square */
	    $size = max($subContext['width'], $subContext['height']);

	    /* Write PPM file with background color and convert to jpeg */
	    list ($ret, $toolkit) = GalleryCoreApi::getToolkitByOperation('image/x-portable-pixmap',
									  'convert-to-image/jpeg');
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null, null);
	    }
	    if (!isset($toolkit)) {
		return array(GalleryStatus::error(ERROR_UNSUPPORTED_OPERATION, __FILE__, __LINE__,
		    'convert-to-image/jpeg image/x-portable-pixmap'), null, null);
	    }
	    $tmpFilename = $platform->tempnam($gallery->getConfig('data.gallery.tmp'), 'sqth_');
	    if (empty($tmpFilename)) {
		return array(GalleryStatus::error(ERROR_BAD_PATH, __FILE__, __LINE__), null, null);
	    }
	    if (!$platform->copy($sourceFilename, $tmpFilename)) {
		@$platform->unlink($tmpFilename);
		return array(GalleryStatus::error(ERROR_PLATFORM_FAILURE, __FILE__, __LINE__),
			     null, null);
	    }
	    if (!($fh = $platform->fopen($destFilename, 'w'))) {
		@$platform->unlink($tmpFilename);
		return array(GalleryStatus::error(ERROR_PLATFORM_FAILURE, __FILE__, __LINE__),
			     null, null);
	    }
	    $platform->fwrite($fh, "P6\n$size $size\n255\n");
	    /* Convert RGB hex string to 3 binary bytes */
	    $row = str_repeat(chr(hexdec(substr($square['color'], 0, 2))) .
			      chr(hexdec(substr($square['color'], 2, 2))) .
			      chr(hexdec(substr($square['color'], 4, 2))), $size);
	    for ($i = 0; $i < $size; $i++) {
		$platform->fwrite($fh, $row);
	    }
	    $platform->fclose($fh);
	    list ($ret) = $toolkit->performOperation(
		'image/x-portable-pixmap', 'convert-to-image/jpeg',
		$destFilename, $destFilename, array());
	    if ($ret->isError()) {
		@$platform->unlink($tmpFilename);
		return array($ret->wrap(__FILE__, __LINE__), null, null);
	    }

	    /* Overlay thumbnail image on square background */
	    list ($ret, $toolkit) = GalleryCoreApi::getToolkitByOperation($mimeType, 'composite');
	    if ($ret->isError()) {
		@$platform->unlink($tmpFilename);
		return array($ret->wrap(__FILE__, __LINE__), null, null);
	    }
	    if (!isset($toolkit)) {
		@$platform->unlink($tmpFilename);
		return array(GalleryStatus::error(ERROR_UNSUPPORTED_OPERATION, __FILE__, __LINE__,
						  "composite $mimeType"), null, null);
	    }
	    $newContext = array('width' => $size, 'height' => $size);
	    list ($ret, $mimeType, $subContext) = $toolkit->performOperation(
		'image/jpeg', 'composite', $destFilename, $destFilename,
		array(substr($tmpFilename, strlen($gallery->getConfig('data.gallery.base'))),
		      $mimeType, $subContext['width'], $subContext['height'], 'center', 0, 0),
		$newContext);
	    @$platform->unlink($tmpFilename);
	    if ($ret->isError()) {
		return array($ret->wrap(__FILE__, __LINE__), null, null);
	    }
	    $sourceFilename = $destFilename;
	}

	if ($sourceFilename != $destFilename) {
	    /* If no work was done (small image, already square) then just copy original */
	    if (!$platform->copy($sourceFilename, $destFilename)) {
		return array(GalleryStatus::error(ERROR_PLATFORM_FAILURE, __FILE__, __LINE__),
			     null, null);
	    }
	}

	$context['width'] = $subContext['width'];
	$context['height'] = $subContext['height'];
	return array(GalleryStatus::success(), $mimeType, $context);
    }

    /**
     * Estimate the dimensions of a GalleryDerivativeImage from its operations and its source.
     *
     * @param object GalleryDerivativeImage the derivative
     * @param object GalleryDerivativeEntity the source
     *                                (probably a GalleryPhotoItem or GalleryMovieItem)
     */
    function estimateDimensions(&$derivative, $source) {
	$handled = false;
	if (method_exists($source, 'getwidth') && method_exists($source, 'getheight')) {
	    $width = $source->getWidth();
	    $height = $source->getHeight();
	    if ($width && $height) {
		$operations = $derivative->getDerivativeOperations();
		if (preg_match('/^(?:thumbnail)\|(\d+)$/', $operations, $matches)) {
		    $targetSize = $matches[1];
		    if (!empty($targetSize)) {
			if (!GalleryUtilities::isA($source, 'GalleryDerivativeImage')) {
			    /*
			     * If the source is also a derivative then assume the real source
			     * is large enough that we can make a square of the desired size.
			     * It's possible the real source is actually smaller than our
			     * $targetSize, but it will be corrected once the derivative is built.
			     */
			    $targetSize = min(min($width, $height), $targetSize);
			}
			$derivative->setWidth($targetSize);
			$derivative->setHeight($targetSize);
			$handled = true;
		    }
		}
	    }
	}

	if (!$handled) {
	    return parent::estimateDimensions($derivative, $source);
	}
    }

    /**
     * Get toolkit for thumbnail operation.
     * @param string mime type
     * @return array object GalleryStatus a status code
     *               object GalleryToolkit
     * @access private
     */
    function _getThumbnailToolkit($mimeType) {
	list ($ret, $data) = GalleryCoreApi::getToolkitOperationMimeTypes('thumbnail');
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}
	if (!empty($data[$mimeType]) && $data[$mimeType][0] == 'SquareThumb') {
	    array_shift($data[$mimeType]);
	}
	if (empty($data[$mimeType])) {
	    return array(GalleryStatus::error(ERROR_UNSUPPORTED_OPERATION, __FILE__, __LINE__,
					      "thumbnail $mimeType"), null);
	}
	list ($ret, $toolkit) =
	    GalleryCoreApi::newFactoryInstanceById('GalleryToolkit', $data[$mimeType][0]);
	if ($ret->isError()) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}
	if (!isset($toolkit)) {
	    return array(GalleryStatus::error(ERROR_UNSUPPORTED_OPERATION, __FILE__, __LINE__,
					      "thumbnail $mimeType"), null);
	}
	return array(GalleryStatus::success(), $toolkit);
    }
}
?>
