forked from Public/pics
		
	
		
			
				
	
	
		
			172 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			172 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
/*****************************************************************************
 | 
						|
 * PhotoMosaic.php
 | 
						|
 * Contains the photo mosaic model, an iterator to create tiled photo galleries.
 | 
						|
 *
 | 
						|
 * Kabuki CMS (C) 2013-2015, Aaron van Geffen
 | 
						|
 *****************************************************************************/
 | 
						|
 | 
						|
class PhotoMosaic
 | 
						|
{
 | 
						|
	private $queue = [];
 | 
						|
 | 
						|
	const NUM_DAYS_CUTOFF = 7;
 | 
						|
	private AssetIterator $iterator;
 | 
						|
 | 
						|
	public function __construct(AssetIterator $iterator)
 | 
						|
	{
 | 
						|
		$this->iterator = $iterator;
 | 
						|
	}
 | 
						|
 | 
						|
	public function __destruct()
 | 
						|
	{
 | 
						|
		$this->iterator->clean();
 | 
						|
	}
 | 
						|
 | 
						|
	public static function getRecentPhotos()
 | 
						|
	{
 | 
						|
		return new self(AssetIterator::getByOptions([
 | 
						|
			'tag' => 'photo',
 | 
						|
			'order' => 'date_captured',
 | 
						|
			'direction' => 'desc',
 | 
						|
			'limit' => 15, // worst case: 3 rows * (portrait + 4 thumbs)
 | 
						|
		]));
 | 
						|
	}
 | 
						|
 | 
						|
	private static function matchTypeMask(Image $image, $type_mask)
 | 
						|
	{
 | 
						|
		return ($type_mask & Image::TYPE_PANORAMA)  && $image->isPanorama()  ||
 | 
						|
			   ($type_mask & Image::TYPE_LANDSCAPE) && $image->isLandscape() ||
 | 
						|
			   ($type_mask & Image::TYPE_PORTRAIT)  && $image->isPortrait();
 | 
						|
	}
 | 
						|
 | 
						|
	private function fetchImage($desired_type = Image::TYPE_PORTRAIT | Image::TYPE_LANDSCAPE | Image::TYPE_PANORAMA, Image $refDateImage = null)
 | 
						|
	{
 | 
						|
		// First, check if we have what we're looking for in the queue.
 | 
						|
		foreach ($this->queue as $i => $image)
 | 
						|
		{
 | 
						|
			// Image has to match the desired type and be taken within a week of the reference image.
 | 
						|
			if (self::matchTypeMask($image, $desired_type) && !(isset($refDateImage) && abs(self::daysApart($image, $refDateImage)) > self::NUM_DAYS_CUTOFF))
 | 
						|
			{
 | 
						|
				unset($this->queue[$i]);
 | 
						|
				return $image;
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		// Check whatever's next up!
 | 
						|
		while (($asset = $this->iterator->next()) && ($image = $asset->getImage()))
 | 
						|
		{
 | 
						|
			// Image has to match the desired type and be taken within a week of the reference image.
 | 
						|
			if (self::matchTypeMask($image, $desired_type) && !(isset($refDateImage) && abs(self::daysApart($image, $refDateImage)) > self::NUM_DAYS_CUTOFF))
 | 
						|
				return $image;
 | 
						|
			else
 | 
						|
				$this->pushToQueue($image);
 | 
						|
		}
 | 
						|
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	private function pushToQueue(Image $image)
 | 
						|
	{
 | 
						|
		$this->queue[] = $image;
 | 
						|
	}
 | 
						|
 | 
						|
	private static function orderPhotos(Image $a, Image $b)
 | 
						|
	{
 | 
						|
		// Show images of highest priority first.
 | 
						|
		$priority_diff = $a->getPriority() - $b->getPriority();
 | 
						|
		if ($priority_diff !== 0)
 | 
						|
			return -$priority_diff;
 | 
						|
 | 
						|
		// In other cases, we'll just show the newest first.
 | 
						|
		return $a->getDateCaptured() > $b->getDateCaptured() ? -1 : 1;
 | 
						|
	}
 | 
						|
 | 
						|
	private static function daysApart(Image $a, Image $b)
 | 
						|
	{
 | 
						|
		return $a->getDateCaptured()->diff($b->getDateCaptured())->days;
 | 
						|
	}
 | 
						|
 | 
						|
	public function getRow()
 | 
						|
	{
 | 
						|
		// Fetch the first image...
 | 
						|
		$image = $this->fetchImage();
 | 
						|
 | 
						|
		// No image at all?
 | 
						|
		if (!$image)
 | 
						|
			return false;
 | 
						|
 | 
						|
		// Is it a panorama? Then we've got our row!
 | 
						|
		elseif ($image->isPanorama())
 | 
						|
			return [[$image], 'panorama'];
 | 
						|
 | 
						|
		// Alright, let's initalise a proper row, then.
 | 
						|
		$photos = [$image];
 | 
						|
		$num_portrait  = $image->isPortrait()  ? 1 : 0;
 | 
						|
		$num_landscape = $image->isLandscape() ? 1 : 0;
 | 
						|
 | 
						|
		// Get an initial batch of non-panorama images to work with.
 | 
						|
		for ($i = 1; $i < 3 && ($image = $this->fetchImage(Image::TYPE_LANDSCAPE | Image::TYPE_PORTRAIT, $image)); $i++)
 | 
						|
		{
 | 
						|
			$num_portrait  += $image->isPortrait()  ? 1 : 0;
 | 
						|
			$num_landscape += $image->isLandscape() ? 1 : 0;
 | 
						|
			$photos[] = $image;
 | 
						|
		}
 | 
						|
 | 
						|
		// Sort photos by priority and date captured.
 | 
						|
		usort($photos, 'self::orderPhotos');
 | 
						|
 | 
						|
		// Three portraits?
 | 
						|
		if ($num_portrait === 3)
 | 
						|
			return [$photos, 'portraits'];
 | 
						|
 | 
						|
		// At least one portrait?
 | 
						|
		if ($num_portrait >= 1)
 | 
						|
		{
 | 
						|
			// Grab two more landscapes, so we can put a total of four tiles on the side.
 | 
						|
			for ($i = 0; $image && $i < 2 && ($image = $this->fetchImage(Image::TYPE_LANDSCAPE | Image::TYPE_PORTRAIT, $image)); $i++)
 | 
						|
				$photos[] = $image;
 | 
						|
 | 
						|
			// We prefer to have the portrait on the side, so prepare to process that first.
 | 
						|
			usort($photos, function($a, $b) {
 | 
						|
				if ($a->isPortrait() && !$b->isPortrait())
 | 
						|
					return -1;
 | 
						|
				elseif ($b->isPortrait() && !$a->isPortrait())
 | 
						|
					return 1;
 | 
						|
				else
 | 
						|
					return self::orderPhotos($a, $b);
 | 
						|
			});
 | 
						|
 | 
						|
			// We might not have a full set of photos, but only bother if we have at least three.
 | 
						|
			if (count($photos) > 3)
 | 
						|
				return [$photos, 'portrait'];
 | 
						|
		}
 | 
						|
 | 
						|
		// One landscape at least, hopefully?
 | 
						|
		if ($num_landscape >= 1)
 | 
						|
		{
 | 
						|
			if (count($photos) === 3)
 | 
						|
			{
 | 
						|
				// We prefer to have the landscape on the side, so prepare to process that first.
 | 
						|
				usort($photos, function($a, $b) {
 | 
						|
					if ($a->isLandscape() && !$b->isLandscape())
 | 
						|
						return -1;
 | 
						|
					elseif ($b->isLandscape() && !$a->isLandscape())
 | 
						|
						return 1;
 | 
						|
					else
 | 
						|
						return self::orderPhotos($a, $b);
 | 
						|
				});
 | 
						|
 | 
						|
				return [$photos, 'landscape'];
 | 
						|
			}
 | 
						|
			elseif (count($photos) === 2)
 | 
						|
				return [$photos, 'duo'];
 | 
						|
			else
 | 
						|
				return [$photos, 'single'];
 | 
						|
		}
 | 
						|
 | 
						|
		// A boring set it is, then.
 | 
						|
		return [$photos, 'row'];
 | 
						|
	}
 | 
						|
}
 |