<?php
/**
 * Copyright 2007-2017 Horde LLC (http://www.horde.org/)
 *
 * See the enclosed file COPYING for license information (LGPL). If you
 * did not receive this file, see http://www.horde.org/licenses/lgpl21.
 *
 * @author    Michael J. Rubinsky <mrubinsk@horde.org>
 * @category  Horde
 * @license   http://www.horde.org/licenses/lgpl21 LGPL-2.1
 * @package   Image
 */

/**
 * Effect for composing multiple images into a single image.
 *
 * The technique for the Polaroid-like stack using the Imagick extension is
 * credited to Mikko Koppanen and is documented at http://valokuva.org
 *
 * @author    Michael J. Rubinsky <mrubinsk@horde.org>
 * @category  Horde
 * @copyright 2007-2017 Horde LLC
 * @license   http://www.horde.org/licenses/lgpl21 LGPL-2.1
 * @package   Image
 */
class Horde_Image_Effect_Im_PhotoStack extends Horde_Image_Effect
{
    /**
     * Valid parameters for the stack effect:
     *   - images: (array) An array of Horde_Image objects to stack. Images
     *             are stacked in a FIFO manner, so that the top-most image is
     *             the last one in this array.
     *   - type: (string) Determines the style for the composition.
     *           'plain' or 'polaroid' are supported.
     *   - resize_height: (integer) The height that each individual thumbnail
     *                    should be resized to before composing on the image.
     *   - padding: (integer) How much padding should we ensure is left around
     *              the active image area?
     *   - background: (string) The background canvas color - this is used as
     *                 the color to set any padding to.
     *   - bordercolor: (string) If using type 'plain' this sets the color of
     *                  the border that each individual thumbnail gets.
     *   - borderwidth: (integer) If using type 'plain' this sets the width of
     *                  the border on each individual thumbnail.
     *   - offset: (integer) If using type 'plain' this determines the amount
     *             of x and y offset to give each successive image when it is
     *             placed on the top of the stack.
     *
     * @var array
     */
    protected $_params = array(
        'type'           => 'plain',
        'resize_height'  => '150',
        'padding'        => 0,
        'background'     => 'none',
        'bordercolor'    => '#333',
        'borderwidth'    => 1,
        'borderrounding' => 10,
        'offset'         => 5
    );

    /**
     * Applies the effect.
     */
    public function apply()
    {
        $i = 1;
        $cnt = count($this->_params['images']);
        if ($cnt <= 0) {
            throw new Horde_Image_Exception('No Images provided.');
        }

        // Start out fresh.
        $this->_image->raw();

        switch ($this->_params['type']) {
        case 'plain':
        case 'rounded':
            // Get top image dimensions, then force each bottom image to the
            // same dimensions.
            $this->_params['images'][$cnt - 1]->resize(
                $this->_params['resize_height'],
                $this->_params['resize_height'],
                true
            );
            $size = $this->_params['images'][$cnt - 1]->getDimensions();
            $xo = $yo = count($this->_params['images'])
                * $this->_params['offset'];
            $ops = '';
            $haveBottom = false;

            foreach ($this->_params['images'] as $image) {
                $image->resize($size['height'], $size['width'], false);
                $xo -= $this->_params['offset'];
                $yo -= $this->_params['offset'];

                if ($this->_params['type'] == 'rounded') {
                    $temp = $this->_roundBorder($image);
                } else {
                    $temp = $image->toFile();
                }
                $this->_image->addFileToClean($temp);
                $ops .= ' \( ' . $temp . ' -background none -thumbnail '
                    . $size['width'] . 'x' . $size['height']
                    . '! -repage +' . $xo . '+' . $yo
                    . ($this->_params['type'] == 'plain' ? ' -bordercolor "#333" -border 1 ' : ' ' )
                    . ((!$haveBottom) ? '\( +clone -shadow 80x3+4+4 \) +swap -mosaic' : '')
                    . ' \) ';
                $haveBottom = true;
            }

            // The first -background none option below is only honored in
            // convert versions before 6.4 it seems. Without it specified as
            // none here, all stacks come out with a white background.
            $this->_image->addPostSrcOperation(
                $ops . ' -background ' . escapeshellarg($this->_params['background'])
                . ' -mosaic -bordercolor ' . escapeshellarg($this->_params['background'])
                . ' -border ' . (integer)$this->_params['padding']);
            break;

        case 'polaroid':
            // Check for im version > 6.3.2
            $ver = $this->_image->getIMVersion();
            if (is_array($ver) && version_compare($ver[0], '6.3.2') >= 0) {
                $ops = '';
                foreach ($this->_params['images'] as $image) {
                    $temp = $image->toFile();
                    // Remember the temp files so we can nuke them later.
                    $this->_image->addFileToClean($temp);

                    // Don't rotate the top image.
                    if ($i++ == $cnt) {
                        $angle = 0;
                    } else {
                        $angle = mt_rand(1, 45);
                        if (mt_rand(1, 2) % 2 === 0) {
                            $angle = $angle * -1;
                        }
                    }
                    $ops .= ' \( ' . $temp
                        . ' -geometry +'
                        . mt_rand(1, $this->_params['resize_height'])
                        . '+' . mt_rand(1, $this->_params['resize_height'])
                        . ' -thumbnail \'' . (integer)$this->_params['resize_height']
                        . 'x' . (integer)$this->_params['resize_height']
                        . '>\' -bordercolor Snow -border 1  -polaroid '
                        . $angle . ' \) ';
                }
                $this->_image->addPostSrcOperation(
                    '-background ' . escapeshellarg($this->_params['background']) . ' ' . $ops
                    . '-mosaic -bordercolor ' . escapeshellarg($this->_params['background'])
                    . ' -border ' . (integer)$this->_params['padding']);
           } else {
                // An attempt at a -polaroid command free version of this
                // effect based on various examples and ideas at
                // http://imagemagick.org
                $ops = '';
                foreach ($this->_params['images'] as $image) {
                    $temp = $image->toFile();
                    $this->_image->addFileToClean($temp);
                    if ($i++ == $cnt) {
                        $angle = 0;
                    } else {
                        $angle = mt_rand(1, 45);
                        if (mt_rand(1, 2) % 2 === 0) {
                            $angle = $angle * -1;
                        }
                    }
                    $ops .= '\( ' . $temp . ' -thumbnail \''
                        . (integer)$this->_params['resize_height']
                        . 'x' . (integer)$this->_params['resize_height']
                        . '>\' -bordercolor "#eee" -border 4 -bordercolor grey90 -border 1 -bordercolor none -background none -rotate '
                        . $angle . ' -background none \( +clone -shadow 60x4+4+4 \) +swap -background none -flatten \) ';
                }
                $this->_image->addPostSrcOperation(
                    '-background none ' . $ops
                    . '-mosaic -trim +repage -bordercolor '
                    . escapeshellarg($this->_params['background'])
                    . ' -border ' . (integer)$this->_params['padding']);
            }
            break;
        }
    }

    private function _roundBorder($image)
    {
        $context = array(
            'tmpdir' => $this->_image->getTmpDir(),
            'convert' => $this->_image->getConvertPath()
        );

        $size = $image->getDimensions();
        $new = new Horde_Image_Im(array('data' => $image->raw()), $context);
        $new->addEffect(
            'RoundCorners',
            array(
                'border' => 2,
                'bordercolor' => '#111',
                'background' => 'none'
            )
        );
        $new->applyEffects();

        return $new->toFile();
    }
}
