forked from 4ducky/4ducky
The PHP sources, templates and old jQuery-based scripts move unchanged into legacy/ for reference. Shared assets (media/, images/, favicon.ico, the default avatar) stay at the repository root. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
172 lines
5.1 KiB
PHP
172 lines
5.1 KiB
PHP
<?php
|
|
|
|
/*
|
|
This class takes an array of images and sorts them into a grid view.
|
|
This is the equivalent of the javascript gridSolve function in picturelicious.js
|
|
with the difference that it runs server side of course.
|
|
*/
|
|
|
|
require_once( 'lib/config.php' );
|
|
|
|
class GridView {
|
|
|
|
public $height = 0;
|
|
protected $grid = array();
|
|
protected $gridWidth = 20;
|
|
|
|
public function __construct( $gridWidth = 20 ) {
|
|
$this->gridWidth = $gridWidth;
|
|
$this->grid = array_fill( 0, $this->gridWidth, 0 );
|
|
}
|
|
|
|
// solve expects an array with thumbnails with the keys name and score
|
|
public function solve( &$thumbs ) {
|
|
if( !is_array( $thumbs ) ) {
|
|
return false;
|
|
}
|
|
|
|
// Order thumbs by importance (score), but leave the keys as they are
|
|
uasort( $thumbs, array( $this, 'compareScore' ) );
|
|
|
|
// How many thumbs get which CSS-Class? The first n thumbnails
|
|
// get the biggest box etc.
|
|
$total = count( $thumbs );
|
|
$classCounts = array();
|
|
$currentCount = 0;
|
|
foreach( Config::$gridView['classes'] as $className => $gc ) {
|
|
$currentCount += $gc['percentage'] * $total;
|
|
$classCounts[$className] = ceil( $currentCount );
|
|
}
|
|
|
|
// Assign a CSS-Class to each thumb
|
|
$currentMax = -1;
|
|
$currentClass = '';
|
|
$j = 0;
|
|
foreach( array_keys( $thumbs ) as $i ) {
|
|
if( $j >= $currentMax ) {
|
|
list( $currentClass, $currentMax ) = each( $classCounts );
|
|
}
|
|
$j++;
|
|
$thumbs[$i]['class'] = $currentClass;
|
|
$thumbs[$i]['thumb'] = Config::$absolutePath . Config::$images['thumbPath'] .
|
|
str_replace( '-', '/', substr( $thumbs[$i]['logged'], 0, 7 ) )
|
|
.'/'. Config::$gridView['classes'][$currentClass]['dir']
|
|
.'/'. $thumbs[$i]['thumb'];
|
|
}
|
|
|
|
// Sort the thumbs back in the order we got them
|
|
ksort( $thumbs );
|
|
|
|
// Now that every thumb has a CSS-Class, we can sort them into our grid
|
|
for( $i = 0; $i < $total; $i++ ) {
|
|
list( $x, $y ) = $this->insert(
|
|
Config::$gridView['classes'][$thumbs[$i]['class']]['width'],
|
|
Config::$gridView['classes'][$thumbs[$i]['class']]['height']
|
|
);
|
|
$thumbs[$i]['left'] = $x * Config::$gridView['gridSize'];
|
|
$thumbs[$i]['top'] = $y * Config::$gridView['gridSize'];
|
|
}
|
|
|
|
// Calculate the final grid height
|
|
for( $i = 0; $i < $this->gridWidth; $i++ ) {
|
|
if( $this->grid[$i] > $this->height ) {
|
|
$this->height = $this->grid[$i];
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Callback for uasort()
|
|
protected function compareScore( $a, $b ) {
|
|
return $a['score'] > $b['score'] ?
|
|
1 :
|
|
( $a['score'] == $b['score'] && $a['votes'] > $b['votes'] ?
|
|
1 :
|
|
-1
|
|
);
|
|
}
|
|
|
|
protected function insert( $boxWidth, $boxHeight ) {
|
|
|
|
// Height of the grid
|
|
$maxHeight = 0;
|
|
|
|
// Height of the grid at the last position
|
|
$currentHeight = $this->grid[0];
|
|
|
|
// Find free spots within the grid and collect them in an arry
|
|
// A spot is a area in the grid with equal height
|
|
$spotWidth = 0; // Width of the spot in grid units
|
|
$spotLeft = 0; // Position in the grid (relative to left border)
|
|
$freeSpots = array();
|
|
|
|
for( $i = 0; $i < $this->gridWidth; $i++ ) {
|
|
|
|
// Height is the same as at the last position?
|
|
// -> increase the size of this spot
|
|
if( $currentHeight == $this->grid[$i] ) {
|
|
$spotWidth++;
|
|
}
|
|
|
|
// The height is different from the last position, and our current spot
|
|
// is wider than 0
|
|
if( ( $currentHeight != $this->grid[$i] || $i+1 == $this->gridWidth) && $spotWidth > 0 ) {
|
|
$freeSpots[] = array( 'width' =>$spotWidth, 'left' => $spotLeft, 'height' => $currentHeight );
|
|
$spotWidth = 1;
|
|
$spotLeft = $i;
|
|
|
|
// Make sure we don't miss the last one
|
|
if( $currentHeight != $this->grid[$i] && $i+1 == $this->gridWidth ) {
|
|
$freeSpots[] = array( 'width' =>$spotWidth, 'left' => $spotLeft, 'height' => $this->grid[$i] );
|
|
}
|
|
}
|
|
|
|
$currentHeight = $this->grid[$i];
|
|
$maxHeight = max( $maxHeight, $this->grid[$i] );
|
|
}
|
|
|
|
// Loop through all found spots and rate them, based on their size and height
|
|
// This way the smallest possible spot in the lowest possible height is filled
|
|
$targetHeight = 0;
|
|
$targetLeft = 0;
|
|
|
|
// Default spot (left border) if we don't find a better one
|
|
for( $i = 0; $i < $boxWidth; $i++ ) {
|
|
if( $this->grid[$i] > $targetHeight ) {
|
|
$targetHeight = $this->grid[$i];
|
|
}
|
|
}
|
|
|
|
$bestScore = -1;
|
|
foreach( array_keys( $freeSpots ) as $i ) {
|
|
|
|
// Difference of the height of this spot to the total height of the grid
|
|
$heightScore = ( $maxHeight - $freeSpots[$i]['height'] );
|
|
|
|
// Relation of the required and the available space
|
|
$widthScore = $boxWidth / $freeSpots[$i]['width'];
|
|
|
|
// The score for this spot is calculated by these both criteria
|
|
$score = $heightScore * $heightScore + $widthScore * 2;
|
|
|
|
// Is the score for this spot higher than for the last one we found?
|
|
if( $freeSpots[$i]['width'] >= $boxWidth && $score > $bestScore ) {
|
|
$targetHeight = $freeSpots[$i]['height'];
|
|
$targetLeft = $freeSpots[$i]['left'];
|
|
$bestScore = $score;
|
|
}
|
|
}
|
|
|
|
$newHeight = $targetHeight + $boxHeight;
|
|
|
|
// Adjust grid height
|
|
for( $j = 0; $j < $boxWidth; $j++ ) {
|
|
$this->grid[$targetLeft + $j] = $newHeight;
|
|
}
|
|
|
|
return array( $targetLeft, $targetHeight );
|
|
}
|
|
}
|
|
|
|
?>
|