Skip to content

Instantly share code, notes, and snippets.

@jthatch
Created October 17, 2016 13:28
Show Gist options
  • Save jthatch/11e9e356bd60984779758734fed0434d to your computer and use it in GitHub Desktop.
Save jthatch/11e9e356bd60984779758734fed0434d to your computer and use it in GitHub Desktop.
A Laravel function to read two directories of pics, landscape and portrait, merging the pics so they fit together
<?php
/**
*
* getPics - Takes two directories of portrait and landscape pictures and merges them to create an ordered 2 column thumbnail gallery.
*
* Instead of reading from an API, it reads pics from the filesystem, caching the results in redis for self::$cache minutes
* There currently two pic sizes, "portrait" and "landscape", possibly more added in the future..
*
* It will take into account the totals of each size, and spread the smaller sizes evenly across the resulting list in sets of 2.
*
* This behaviour is required to create a nice looking thumbnail list,
* where portrait images which take up a higher vertical hight are not interspersed with landscape pics on the same row,
* which creates undesirable empty space.
*
* E.G.: If there are 40 portrait pics and 10 landscape they will be placed like follows: (P = portrait, L = landscape)
* P P
* L L
* P P
* P P
* P P
* P P
* L L
* .. and so on
* With the Landscape pics spread evently across the length of the list, rather than grouped at the beginning
*
*
* Files are located here on the filesystem:
* app_path()/pics/{site}/{size}/large
*
* - jthatch 14/12/15
* @param string $onlySize
* @param integer $columns
* @return array
*/
private function getPics($onlySize = '', $columns = 2)
{
$pics = Cache::remember("mobile.wallpaper.{$this->site->hash}", self::$cache, function() use ($onlySize, $columns) {
$pics = [];
// define our storage instance
$disk = Storage::disk('local');
$path = "pics/{$this->site->hash}";
//$sizes = [];
//$sizes = ['portrait', 'landscape'];
$sizes = $disk->directories($path);
if (empty($sizes)) {
Log::error("No pics found in {$path}");
}
// just take the last path
array_walk($sizes, function (&$size) {
if (stristr($size, DIRECTORY_SEPARATOR)) {
$paths = explode(DIRECTORY_SEPARATOR, $size);
$size = array_pop($paths);
}
});
// if we just want a single set of pics, e.g. only portrait, we call the function with the size we want
if ($onlySize && in_array($onlySize, $sizes))
$sizes = [$onlySize];
/**
* Iterate over the sizes.
* Read the images from the filesystem and create an array of image objects stored in $pics[$size]
*/
foreach ($sizes as $size) {
// define our path
$path = "pics/{$this->site->hash}/{$size}/large";
try {
$files = $disk->allFiles($path);
if (empty ($files)) {
Log::error("No pics found in {$path}");
} else {
// Only create entry if there are any pics
$pics[$size] = [];
foreach ($files as $f) {
$parts = pathinfo($f);
$filename = $parts['filename']; // 123
// initializing a PHP object based on array is faster than invoking a new stdClass()..
$pics[$size][] = (object)[
'id' => $filename,
'large' => '/' . $f,
'thumb' => "/" . str_replace(["/large/", "."], ["/thumbs/", "_thumb."], $f),
'url' => !!$this->site->dynamic_videos_serve_static ? url("pics/{$filename}.html") : url("wallpapers/{$filename}"),
'size' => $size
];
}
}
} catch (\ErrorException $e) {
Log::error("Error accessing {$path}: {$e->getMessage()}");
}
}
/**
* If we have multiple sizes then let's distrubute the sizes according to our rules defined above.
*/
if (sizeof($pics) > 1) {
// store our incrementing counters
$counters = [];
// store the biggest arrays first
$pic_order = [];
// store the total pics
$total_pics = 0;
foreach (array_keys($pics) as $size) {
$counters[$size] = (object)[
'total' => sizeof($pics[$size])
];
// use total is the array index referencing the size so we can sort by biggest below
$pic_order[$counters[$size]->total] = $size;
$total_pics += $counters[$size]->total;
};
// start with the biggest first and use this as our "base"
rsort($pic_order);
// the biggest size will be the first element of pic_order
$biggest = $pic_order[0];
/* Now that we know the biggest size, find the frequency that these sizes should be
* inserted into the list, represented by every xth iteration
*/
foreach ($pic_order as $size) {
$counters[$size]->frequency = ceil(
$counters[$biggest]->total /
($counters[$size]->total > 0 ? $counters[$size]->total : 1)
);
}
/**
* Frequency will store our pics in an order manner
*/
$frequency = [];
$counter = 0;
// we do this in batches of $columns
for ($i = 0; $i < $total_pics; $i += $columns) {
// using the pic order lets iterate over our counters
foreach ($pic_order as $key) {
$size = &$counters[$key];
if ($counter % $size->frequency == 0) {
if ($size->total > 0) {
// to avoid the ghost array if we get down to 1 left we don't want to pull 2 by mistake
$ceil = min($columns, $size->total);
for ($j = 0; $j < $ceil; $j++) {
$frequency[] = array_pop($pics[$key]);
//$frequency[] = $key;
}
$size->total -= $columns;
}
}
}
$counter++;
}
return $frequency;
} // if we have just one that's easy
else if (sizeof($pics) == 1) {
return array_pop($pics);
} else {
return [];
}
});
return $pics;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment