openvk/Web/Util/Makima/Makima.php
mrilyew e0f4570bbd
Posts: add source param (#934)
* Chopin

- Полностью переписана та часть где про источник поста
- Исправлен метод video.search
- Сокращено число запросов в БД у шаблона поста
- Удалена ссылка на прикреплятор заметок потому что low quality
- Исправлен баг с прикреплённой заметкой в api, но только если ты указал версию.
- Исправлены проблемы с кешированными спрайтшитами

* Chopin 2
2024-11-03 16:39:00 +03:00

305 lines
14 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php declare(strict_types=1);
namespace openvk\Web\Util\Makima;
use openvk\Web\Models\Entities\Photo;
class Makima
{
private $photos;
const ORIENT_WIDE = 0;
const ORIENT_REGULAR = 1;
const ORIENT_SLIM = 2;
function __construct(array $photos)
{
if(sizeof($photos) < 2)
throw new \LogicException("Minimum attachment count for tiled layout is 2");
$this->photos = $photos;
}
private function getOrientation(Photo $photo, &$ratio): int
{
[$width, $height] = $photo->getDimensions();
$ratio = $width / $height;
if($ratio >= 1.2)
return Makima::ORIENT_WIDE;
else if($ratio >= 0.8)
return Makima::ORIENT_REGULAR;
else
return Makima::ORIENT_SLIM;
}
private function calculateMultiThumbsHeight(array $ratios, float $w, float $m): float
{
return ($w - (sizeof($ratios) - 1) * $m) / array_sum($ratios);
}
private function extractSubArr(array $arr, int $from, int $to): array
{
return array_slice($arr, $from, sizeof($arr) - $from - (sizeof($arr) - $to));
}
function computeMasonryLayout(float $maxWidth, float $maxHeight): MasonryLayout
{
$orients = [];
$ratios = [];
$count = sizeof($this->photos);
$result = new MasonryLayout;
foreach($this->photos as $photo) {
$orients[] = $this->getOrientation($photo, $ratio);
$ratios[] = $ratio;
}
$avgRatio = array_sum($ratios) / sizeof($ratios);
if($maxWidth < 0)
$maxWidth = $maxHeight = 510;
$maxRatio = $maxWidth / $maxHeight;
$marginWidth = $marginHeight = 2;
switch($count) {
case 2:
if(
$orients == [Makima::ORIENT_WIDE, Makima::ORIENT_WIDE] # two wide pics
&& $avgRatio > (1.4 * $maxRatio) && abs($ratios[0] - $ratios[1]) < 0.2 # that can be positioned on top of each other
) {
$computedHeight = ceil( min( $maxWidth / $ratios[0], min( $maxWidth / $ratios[1], ($maxHeight - $marginHeight) / 2 ) ) );
$result->colSizes = [1];
$result->rowSizes = [1, 1];
$result->width = ceil($maxWidth);
$result->height = $computedHeight * 2;
$result->tiles = [new ThumbTile(1, 1, $maxWidth, $computedHeight), new ThumbTile(1, 1, $maxWidth, $computedHeight)];
} else if(
$orients == [Makima::ORIENT_WIDE, Makima::ORIENT_WIDE]
|| $orients == [Makima::ORIENT_REGULAR, Makima::ORIENT_REGULAR] # two normal pics of same ratio
) {
$computedWidth = ($maxWidth - $marginWidth) / 2;
$height = min( $computedWidth / $ratios[0], min( $computedWidth / $ratios[1], $maxHeight ) );
$result->colSizes = [1, 1];
$result->rowSizes = [1];
$result->width = ceil($maxWidth);
$result->height = ceil($height);
$result->tiles = [new ThumbTile(1, 1, $computedWidth, $height), new ThumbTile(1, 1, $computedWidth, $height)];
} else /* next to each other, different ratios */ {
$w0 = (
($maxWidth - $marginWidth) / $ratios[1] / ( (1 / $ratios[0]) + (1 / $ratios[1]) )
);
$w1 = $maxWidth - $w0 - $marginWidth;
$h = min($maxHeight, min($w0 / $ratios[0], $w1 / $ratios[1]));
$result->colSizes = [ceil($w0), ceil($w1)];
$result->rowSizes = [1];
$result->width = ceil($w0 + $w1 + $marginWidth);
$result->height = ceil($h);
$result->tiles = [new ThumbTile(1, 1, $w0, $h), new ThumbTile(1, 1, $w1, $h)];
}
break;
case 3:
# Three wide photos, we will put two of them below and one on top
if($orients == [Makima::ORIENT_WIDE, Makima::ORIENT_WIDE, Makima::ORIENT_WIDE]) {
$hCover = min($maxWidth / $ratios[0], ($maxHeight - $marginHeight) * (2 / 3));
$w2 = ($maxWidth - $marginWidth) / 2;
$h = min($maxHeight - $hCover - $marginHeight, min($w2 / $ratios[1], $w2 / $ratios[2]));
$result->colSizes = [1, 1];
$result->rowSizes = [ceil($hCover), ceil($h)];
$result->width = ceil($maxWidth);
$result->height = ceil($marginHeight + $hCover + $h);
$result->tiles = [
new ThumbTile(2, 1, $maxWidth, $hCover),
new ThumbTile(1, 1, $w2, $h), new ThumbTile(1, 1, $w2, $h),
];
} else /* Photos have different sizes or are not wide, so we will put one to left and two to the right */ {
$wCover = min($maxHeight * $ratios[0], ($maxWidth - $marginWidth) * (3 / 4));
$h1 = ($ratios[1] * ($maxHeight - $marginHeight) / ($ratios[2] + $ratios[1]));
$h0 = $maxHeight - $marginHeight - $h1;
$w = min($maxWidth - $marginWidth - $wCover, min($h1 * $ratios[2], $h0 * $ratios[1]));
$result->colSizes = [ceil($wCover), ceil($w)];
$result->rowSizes = [ceil($h0), ceil($h1)];
$result->width = ceil($w + $wCover + $marginWidth);
$result->height = ceil($maxHeight);
$result->tiles = [
new ThumbTile(1, 2, $wCover, $maxHeight), new ThumbTile(1, 1, $w, $h0),
new ThumbTile(1, 1, $w, $h1),
];
}
break;
case 4:
# Four wide photos, we will put one to the top and rest below
if($orients == [Makima::ORIENT_WIDE, Makima::ORIENT_WIDE, Makima::ORIENT_WIDE, Makima::ORIENT_WIDE]) {
$hCover = min($maxWidth / $ratios[0], ($maxHeight - $marginHeight) / (2 / 3));
$h = ($maxWidth - 2 * $marginWidth) / (array_sum($ratios) - $ratios[0]);
$w0 = $h * $ratios[1];
$w1 = $h * $ratios[2];
$w2 = $h * $ratios[3];
$h = min($maxHeight - $marginHeight - $hCover, $h);
$result->colSizes = [ceil($w0), ceil($w1), ceil($w2)];
$result->rowSizes = [ceil($hCover), ceil($h)];
$result->width = ceil($maxWidth);
$result->height = ceil($hCover + $marginHeight + $h);
$result->tiles = [
new ThumbTile(3, 1, $maxWidth, $hCover),
new ThumbTile(1, 1, $w0, $h), new ThumbTile(1, 1, $w1, $h), new ThumbTile(1, 1, $w2, $h),
];
} else /* Four photos, we will put one to the left and rest to the right */ {
$wCover = min($maxHeight * $ratios[0], ($maxWidth - $marginWidth) * (2 / 3));
$w = ($maxHeight - 2 * $marginHeight) / (1 / $ratios[1] + 1 / $ratios[2] + 1 / $ratios[3]);
$h0 = $w / $ratios[1];
$h1 = $w / $ratios[2];
$h2 = $w / $ratios[3] + $marginHeight;
$w = min($w, $maxWidth - $marginWidth - $wCover);
$result->colSizes = [ceil($wCover), ceil($w)];
$result->rowSizes = [ceil($h0), ceil($h1), ceil($h2)];
$result->width = ceil($wCover + $marginWidth + $w);
$result->height = ceil($maxHeight);
$result->tiles = [
new ThumbTile(1, 3, $wCover, $maxHeight), new ThumbTile(1, 1, $w, $h0),
new ThumbTile(1, 1, $w, $h1),
new ThumbTile(1, 1, $w, $h2),
];
}
break;
default:
// как лопать пузырики
$ratiosCropped = [];
if($avgRatio > 1.1) {
foreach($ratios as $ratio)
$ratiosCropped[] = max($ratio, 1.0);
} else {
foreach($ratios as $ratio)
$ratiosCropped[] = min($ratio, 1.0);
}
$tries = [];
$firstLine;
$secondLine;
$thirdLine;
# Try one line:
$tries[$firstLine = $count] = [$this->calculateMultiThumbsHeight($ratiosCropped, $maxWidth, $marginWidth)];
# Try two lines:
for($firstLine = 1; $firstLine < ($count - 1); $firstLine++) {
$secondLine = $count - $firstLine;
$key = "$firstLine&$secondLine";
$tries[$key] = [
$this->calculateMultiThumbsHeight(array_slice($ratiosCropped, 0, $firstLine), $maxWidth, $marginWidth),
$this->calculateMultiThumbsHeight(array_slice($ratiosCropped, $firstLine), $maxWidth, $marginWidth),
];
}
# Try three lines:
for($firstLine = 1; $firstLine < ($count - 2); $firstLine++) {
for($secondLine = 1; $secondLine < ($count - $firstLine - 1); $secondLine++) {
$thirdLine = $count - $firstLine - $secondLine;
$key = "$firstLine&$secondLine&$thirdLine";
$tries[$key] = [
$this->calculateMultiThumbsHeight(array_slice($ratiosCropped, 0, $firstLine), $maxWidth, $marginWidth),
$this->calculateMultiThumbsHeight($this->extractSubArr($ratiosCropped, $firstLine, $firstLine + $secondLine), $maxWidth, $marginWidth),
$this->calculateMultiThumbsHeight($this->extractSubArr($ratiosCropped, $firstLine + $secondLine, sizeof($ratiosCropped)), $maxWidth, $marginWidth),
];
}
}
# Now let's find the most optimal configuration:
$optimalConfiguration = $optimalDifference = NULL;
foreach($tries as $config => $heights) {
$config = explode('&', (string) $config); # да да стринговые ключи пхп даже со стриктайпами автокастует к инту (см. 187)
$confH = $marginHeight * (sizeof($heights) - 1);
foreach($heights as $h)
$confH += $h;
$confDiff = abs($confH - $maxHeight);
if(sizeof($config) > 1)
if($config[0] > $config[1] || sizeof($config) >= 2 && $config[1] > $config[2])
$confDiff *= 1.1;
if(!$optimalConfiguration || $confDigff < $optimalDifference) {
$optimalConfiguration = $config;
$optimalDifference = $confDiff;
}
}
$thumbsRemain = $this->photos;
$ratiosRemain = $ratiosCropped;
$optHeights = $tries[implode('&', $optimalConfiguration)];
$k = 0;
$result->width = ceil($maxWidth);
$result->rowSizes = [sizeof($optHeights)];
$result->tiles = [];
$totalHeight = 0.0;
$gridLineOffsets = [];
$rowTiles = []; // vector<vector<ThumbTile>>
for($i = 0; $i < sizeof($optimalConfiguration); $i++) {
$lineChunksNum = $optimalConfiguration[$i];
$lineThumbs = [];
for($j = 0; $j < $lineChunksNum; $j++)
$lineThumbs[] = array_shift($thumbsRemain);
$lineHeight = $optHeights[$i];
$totalHeight += $lineHeight;
$result->rowSizes[$i] = ceil($lineHeight);
$totalWidth = 0;
$row = [];
for($j = 0; $j < sizeof($lineThumbs); $j++) {
$thumbRatio = array_shift($ratiosRemain);
if($j == sizeof($lineThumbs) - 1)
$w = $maxWidth - $totalWidth;
else
$w = $thumbRatio * $lineHeight;
$totalWidth += ceil($w);
if($j < (sizeof($lineThumbs) - 1) && !in_array($totalWidth, $gridLineOffsets))
$gridLineOffsets[] = $totalWidth;
$tile = new ThumbTile(1, 1, $w, $lineHeight);
$result->tiles[$k++] = $row[] = $tile;
}
$rowTiles[] = $row;
}
sort($gridLineOffsets, SORT_NUMERIC);
$gridLineOffsets[] = $maxWidth;
$result->colSizes = [$gridLineOffsets[0]];
for($i = sizeof($gridLineOffsets) - 1; $i > 0; $i--)
$result->colSizes[$i] = $gridLineOffsets[$i] - $gridLineOffsets[$i - 1];
foreach($rowTiles as $row) {
$columnOffset = 0;
foreach($row as $tile) {
$startColumn = $columnOffset;
$width = 0;
$tile->colSpan = 0;
for($i = $startColumn; $i < sizeof($result->colSizes); $i++) {
$width += $result->colSizes[$i];
$tile->colSpan++;
if($width == $tile->width)
break;
}
$columnOffset += $tile->colSpan;
}
}
$result->height = ceil($totalHeight + $marginHeight * (sizeof($optHeights) - 1));
break;
}
return $result;
}
}