mirror of
https://github.com/openvk/openvk
synced 2024-12-23 17:12:01 +03:00
draft masonry picture layout in posts xddd
где мои опиаты???
This commit is contained in:
parent
83714213f2
commit
c566c7e402
8 changed files with 387 additions and 5 deletions
|
@ -1,6 +1,7 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Models\Entities\Traits;
|
||||
use openvk\Web\Models\Entities\Attachable;
|
||||
use openvk\Web\Models\Entities\{Attachable, Photo};
|
||||
use openvk\Web\Util\Makima\Makima;
|
||||
use Chandler\Database\DatabaseConnection;
|
||||
|
||||
trait TAttachmentHost
|
||||
|
@ -30,6 +31,45 @@ trait TAttachmentHost
|
|||
}
|
||||
}
|
||||
|
||||
function getChildrenWithLayout(int $w, int $h = -1): object
|
||||
{
|
||||
if($h < 0)
|
||||
$h = $w;
|
||||
|
||||
$children = $this->getChildren();
|
||||
$skipped = $photos = $result = [];
|
||||
foreach($children as $child) {
|
||||
if($child instanceof Photo) {
|
||||
$photos[] = $child;
|
||||
continue;
|
||||
}
|
||||
|
||||
$skipped[] = ["100%", "unset", $child, "unset"];
|
||||
}
|
||||
|
||||
$height = "unset";
|
||||
$width = $w;
|
||||
if(sizeof($photos) < 2) {
|
||||
if(isset($photos[0]))
|
||||
$result[] = ["100%", "unset", $photos[0], "unset"];
|
||||
} else {
|
||||
$mak = new Makima($photos);
|
||||
$layout = $mak->computeMasonryLayout($w, $h);
|
||||
$height = $layout->height;
|
||||
$width = $layout->width;
|
||||
for($i = 0; $i < sizeof($photos); $i++) {
|
||||
$tile = $layout->tiles[$i];
|
||||
$result[] = [$tile->width . "px", $tile->height . "px", $photos[$i], "left"];
|
||||
}
|
||||
}
|
||||
|
||||
return (object) [
|
||||
"width" => $width . "px",
|
||||
"height" => $height . "px",
|
||||
"tiles" => array_merge($result, $skipped),
|
||||
];
|
||||
}
|
||||
|
||||
function attach(Attachable $attachment): void
|
||||
{
|
||||
DatabaseConnection::i()->getContext()
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
{/block}
|
||||
|
||||
{block content}
|
||||
{php $GLOBALS["_bigWall"] = 1}
|
||||
|
||||
<div class="tabs">
|
||||
<div n:attr="id => (isset($globalFeed) ? 'ki' : 'activetabs')" class="tab">
|
||||
<a n:attr="id => (isset($globalFeed) ? 'ki' : 'act_tab_a')" href="/feed">{_my_news}</a>
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
{if !$attachment->isDeleted()}
|
||||
{var $link = "/photo" . ($attachment->isAnonymous() ? ("s/" . base_convert((string) $attachment->getId(), 10, 32)) : $attachment->getPrettyId())}
|
||||
<a href="{$link}">
|
||||
<img class="media" src="{$attachment->getURLBySizeId('normal')}" alt="{$attachment->getDescription()}" />
|
||||
<img class="media media_makima" src="{$attachment->getURLBySizeId('normal')}" alt="{$attachment->getDescription()}" />
|
||||
</a>
|
||||
{else}
|
||||
<a href="javascript:alert('{_attach_no_longer_available}');">
|
||||
|
|
|
@ -52,9 +52,14 @@
|
|||
<div class="text" id="text{$post->getPrettyId()}">
|
||||
{$post->getText()|noescape}
|
||||
|
||||
<div n:ifcontent class="attachments_b">
|
||||
<div class="attachment" n:foreach="$post->getChildren() as $attachment" data-localized-nsfw-text="{_nsfw_warning}">
|
||||
{include "../attachment.xml", attachment => $attachment}
|
||||
{var $width = ($GLOBALS["_bigWall"] ?? false) ? 550 : 320}
|
||||
{if isset($GLOBALS["_nesAttGloCou"])}
|
||||
{var $width = $width - 70 * $GLOBALS["_nesAttGloCou"]}
|
||||
{/if}
|
||||
{var $attachmentsLayout = $post->getChildrenWithLayout($width)}
|
||||
<div n:ifcontent class="attachments_b" style="height: {$attachmentsLayout->height|noescape}; width: {$attachmentsLayout->width|noescape};">
|
||||
<div class="attachment" n:foreach="$attachmentsLayout->tiles as $attachment" style="float: {$attachment[3]|noescape}; width: {$attachment[0]|noescape}; height: {$attachment[1]|noescape};" data-localized-nsfw-text="{_nsfw_warning}">
|
||||
{include "../attachment.xml", attachment => $attachment[2]}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
305
Web/Util/Makima/Makima.php
Normal file
305
Web/Util/Makima/Makima.php
Normal file
|
@ -0,0 +1,305 @@
|
|||
<?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 $h, 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;
|
||||
$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], $w / $ratios[1]));
|
||||
|
||||
$result->colSizes = [ceil($w0), ceil($w1)];
|
||||
$result->rowSizes = [1];
|
||||
$result->width = ceil($w0 + $w1 + $marginWidth);
|
||||
$result->height = ceil($height);
|
||||
$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 - $margin, 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, $h1),
|
||||
];
|
||||
}
|
||||
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('&', $config);
|
||||
$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($optConf); $i++) {
|
||||
$lineChunksNum = $optConf[$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;
|
||||
}
|
||||
|
||||
$result->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;
|
||||
}
|
||||
}
|
10
Web/Util/Makima/MasonryLayout.php
Normal file
10
Web/Util/Makima/MasonryLayout.php
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Util\Makima;
|
||||
|
||||
class MasonryLayout {
|
||||
public $colSizes;
|
||||
public $rowSizes;
|
||||
public $tiles;
|
||||
public $width;
|
||||
public $height;
|
||||
}
|
14
Web/Util/Makima/ThumbTile.php
Normal file
14
Web/Util/Makima/ThumbTile.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php declare(strict_types=1);
|
||||
namespace openvk\Web\Util\Makima;
|
||||
|
||||
class ThumbTile {
|
||||
public $width;
|
||||
public $height;
|
||||
public $rowSpan;
|
||||
public $colSpan;
|
||||
|
||||
function __construct(int $rs, int $cs, float $w, float $h)
|
||||
{
|
||||
[$this->width, $this->height, $this->rowSpan, $this->colSpan] = [ceil($w), ceil($h), $rs, $cs];
|
||||
}
|
||||
}
|
|
@ -651,6 +651,12 @@ h4 {
|
|||
image-rendering: -webkit-optimize-contrast;
|
||||
}
|
||||
|
||||
.post-content .media_makima {
|
||||
width: calc(100% - 4px);
|
||||
height: calc(100% - 4px);
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.post-signature {
|
||||
margin: 4px;
|
||||
margin-bottom: 2px;
|
||||
|
|
Loading…
Reference in a new issue