diff --git a/models/PhotoMosaic.php b/models/PhotoMosaic.php index f4e13392..8fc0eef8 100644 --- a/models/PhotoMosaic.php +++ b/models/PhotoMosaic.php @@ -8,11 +8,13 @@ class PhotoMosaic { - const NUM_DAYS_CUTOFF = 7; private AssetIterator $iterator; private $layouts; + private $processedImages = 0; private $queue = []; + const NUM_DAYS_CUTOFF = 7; + public function __construct(AssetIterator $iterator) { $this->iterator = $iterator; @@ -31,7 +33,8 @@ class PhotoMosaic 'panorama' => [Image::TYPE_PANORAMA], // Big-small juxtapositions - 'portrait' => [Image::TYPE_PORTRAIT, Image::TYPE_LANDSCAPE, Image::TYPE_LANDSCAPE], + 'portrait' => [Image::TYPE_PORTRAIT, Image::TYPE_LANDSCAPE, Image::TYPE_LANDSCAPE, + Image::TYPE_LANDSCAPE, Image::TYPE_LANDSCAPE], 'landscape' => [Image::TYPE_LANDSCAPE, Image::TYPE_LANDSCAPE, Image::TYPE_LANDSCAPE], // Single row of three @@ -79,87 +82,39 @@ class PhotoMosaic 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++) + $currentImages = []; + $selectedLayout = null; + foreach ($this->layouts as $layout => $requiredImageTypes) { - $num_portrait += $image->isPortrait() ? 1 : 0; - $num_landscape += $image->isLandscape() ? 1 : 0; - $photos[] = $image; - } + // Fetch images per the layout requirements + // TODO: pass last image back into fetchImage for date cutoff purposes + $currentImages = array_filter(array_map([$this, 'fetchImage'], $requiredImageTypes)); - // 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) + // Matching requirements? + if (count($currentImages) === count($requiredImageTypes)) { - // 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']; + $selectedLayout = $layout; + break; } - elseif (count($photos) === 2) - return [$photos, 'duo']; - else - return [$photos, 'single']; + + // Push mismatches back into the queue + array_map([$this, 'pushToQueue'], $currentImages); + $currentImages = []; } - // Last resort: majority vote - if ($num_portrait > $num_landscape) - return [$photos, 'portraits']; + if ($selectedLayout) + { + // Hurray, we've got something that works + usort($currentImages, [$this, 'orderPhotos']); + $this->processedImages += count($currentImages); + return [$currentImages, $selectedLayout]; + } else - return [$photos, 'landscapes']; + { + // Ensure we have no images left in the iterator before giving up + assert($this->processedImages === $this->iterator->num()); + return false; + } } private static function matchTypeMask(Image $image, $type_mask) @@ -171,13 +126,13 @@ class PhotoMosaic 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; + // Leave images of different types as-is + if ($a->isLandscape() !== $b->isLandscape()) + return 0; - // In other cases, we'll just show the newest first. - return $a->getDateCaptured() <=> $b->getDateCaptured(); + // Otherwise, show images of highest priority first + $priority_diff = $a->getPriority() - $b->getPriority(); + return -$priority_diff; } private function pushToQueue(Image $image)