photos = $photos; } private function getOrientation($photo, &$ratio): int { [$width, $height] = $photo->getDimensions(); $ratio = $width / $height; if ($ratio >= 1.2) { return Makima::ORIENT_WIDE; } elseif ($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)); } public 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)]; } elseif ( $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> 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; } }