Какой алгоритм используется для отображения нескольких фото в виде мозаики?
Например, как в ВК при загрузке фотографий. Речь идет о таком отображении:
UPD:
Возможно, для вывода изображения используется алгоритм TreeMap. Но не ясно, как задавать "веса" изображениям.
Ответы
Ответ 1
Алгоритм с сохранением порядка следования картинок.
Определяем количество строк как sqrt( сумма отношений сторон всех картинок / отношение сторон блока ), где отношение сторон определяется как ширина делённая на высоту.
Определяем высоту строки как высоту блока делённую на число строк.
Определяем общую длину всех картинок масштабированных к высоте строки.
Заполняем строки так чтобы ширина строки была максимально приближена к ширине оставшихся картинок делённой на количество оставшихся строк.
Перераспределяем высоты строк. Для этого для каждой строки считаем новую высоту так
чтобы ширина строки стала равной ширине блока. разности между новыми и старыми высотам
строк суммируем отдельно по переполненным и недозаполненным строкам. В зависимости от того какая разность больше пропорционально уменьшаем изменения высот в одной из этих групп, чтобы эти суммы сравнялись. (Галочка в примере отменяет последнее выравнивание, что позволяет сохранить соотношения сторон картинок, но нарушает высоту блока).
Для строк у которых осталось свободное пространство (до ширины блока) масштабируе
картинки так, чтобы занять всю ширину блока, после чего обрезаем их по высоте с центрированием по вертикали.
В строках, где нет свободного пространства масштабируем картинки так, чтобы их высот
была равна высоте строки, и обрезаем их по ширине с центрированием по горизонтали пропорционально их отношению сторон так, чтобы общая ширина строки была равна ширине блока.
var iimgs=document.getElementById("iimgs").childNodes;
var c=document.getElementById("c");
var images=[];
function packimgs(freeH) {
var padding = 5;
var divWidth = 510+padding;
var divHeight = 350+padding;
var w=0;
for(var i=0;i 1.5*w/nrows) nrows-= Math.round(images[i].aspect*nrows/w)-1;
if(nrows<1) nrows=1;
var rows= [];
var rowHeight= divHeight/nrows - padding;
w= rowHeight*w + padding*images.length;
for(var j=0; j0 && Math.abs(rowWidth-w/(nrows-1-j)) < Math.abs(rowWidth-w/(nrows-1-j)+imgWidth*2)) {
rowWidth=0; j++;
}
rows[j].push(images[i]);
rows[j].space-= imgWidth;
rowWidth+= imgWidth;
w-= imgWidth;
}
// Подгон высот
var dhp=0, dhm=0, pn=0;
for(j=0; jrowHeight) {
dhp+= rows[j].height-rowHeight;
pn++;
} else dhm+= rowHeight-rows[j].height;
}
// второй этап подгона высот (пропустить если не нужно точно соблюдать высоту блока)
if(!freeH&&dhp!=dhm) for(j=0; jrowHeight && dhp>dhm)
rows[j].height= rowHeight + (rows[j].height-rowHeight)*dhm/dhp;
else if(rows[j].height0? rowHeight*rowWidth1/rowWidth : rows[j].height;
for(i=0;i0) {
cell.style.height= rows[j].height+"px";
rows[j][i].style.marginTop= (rows[j].height-rowHeight1)/2+"px";
} else {
var cwidth= rows[j][i].aspect*rowWidth1*rowHeight/rowWidth;
cell.style.width= cwidth+"px";
rows[j][i].style.marginLeft= (cwidth-rows[j][i].width)/2+"px";
}
}
}
}
function chlimit(i) {
c.innerHTML="";
packimgs(i.checked);
}
images.loaded=0;
for(i=0;i
Алгоритм с изменением порядка картинок.
Определяем количество строк как sqrt( сумма отношений сторон всех картинок / отношение сторон блока ), где отношение сторон определяется как ширина делённая на высоту.
Определяем высоту строки как высоту блока делённую на число строк.
Сортируем картинки по соотношению сторон, ставя в начало самые широкие.
Распределяем картинки по строкам, помещая очередную картинку в строку с наибольши
свободным пространством, которое определяется как ширина блока минус сумма ширин картинок смасштабированных так, чтобы их высота была равна высоте строки.
Для строк у которых осталось свободное пространство (до ширины блока) масштабируе
картинки так, чтобы занять всю ширину блока, после чего обрезаем их по высоте с центрированием по вертикали.
В строках, где нет свободного пространства масштабируем картинки так, чтобы их высот
была равна высоте строки, и обрезаем их по ширине с центрированием по горизонтали пропорционально их отношению сторон так, чтобы общая ширина строки была равна ширине блока.
var iimgs=document.getElementById("iimgs").childNodes;
var c=document.getElementById("c");
var images=[];
function packimgs() {
var divWidth = 510;
var divHeight = 350;
var padding = 5;
var w=0;
for(var i=0;i 1.5*w/nrows) nrows-= Math.round(images[i].aspect*nrows/w)-1;
if(nrows<1) nrows=1;
var rows= [];
var rowHeight= (divHeight-padding*(nrows-1))/nrows;
for(var j=0; j1) rows[maxj].space-= padding;
}
for(j=0; j0? rowHeight*rowWidth1/rowWidth : rowHeight;
for(i=0;i0) {
cell.style.height= rowHeight+"px";
rows[j][i].style.marginTop= (rowHeight-rowHeight1)/2+"px";
} else {
var cwidth= rows[j][i].width*rowWidth1/rowWidth;
cell.style.width= cwidth+"px";
rows[j][i].style.marginLeft= (cwidth-rows[j][i].width)/2+"px";
}
}
}
}
for(i=0;i
Ответ 2
Я так и не нашел универсального решения, но опишу как это работает сейчас, може
быть это кому-то поможет.
Сейчас я исходя из кол-ва картиночек делю высоту (350 пикселей) на N-ое кол-во строк. Максимум - три, т.к. картиночек может быть всего 10.
Далее, я заполняю строки следующим образом:
Считаю для каждой строки суммарное соотношение сторон
В строку у которой это значение самое низкое добавляю картинку
Если картинки еще есть - пункт 1.
Теперь я задаю ширину картинкам (высота, напомню, это просто 350 / кол-во строк). Делаю я это так:
Выбираю очередную строку
Достаю очередную картинку
Ширина картинки = 510 * (соотношение_сторон_картинки /
суммарное_соотношение_сторон_картинок_в_сроке)
И т.д.
Ну и код, который работает по этому алгоритму:
'use strict';
angular.module('imageGrid').controller('imageGridController', function($scope) {
var divWidth = 510;
var divHeight = 350;
var padding = 5;
var imagesList = $scope.photos;
angular.forEach(imagesList, function(image, index) {
image.aspectRatio = image.width / image.height;
image.blockWidth = 0;
image.blockHeight = 0;
image.marginBottom = 0;
image.marginRight = 0;
}, this);
var sumOfWidth = function(set) {
var totalWidth = 0;
if (set.length > 0) {
for (var i = 0; i < set.length; i++) {
totalWidth += set[i].width;
}
}
return totalWidth;
}
var sumOfAspectRatio = function(set) {
var totalAspectRatio = 0;
if (set.length > 0) {
for (var i = 0; i < set.length; i++) {
totalAspectRatio += set[i].aspectRatio;
}
}
return totalAspectRatio;
}
var medianAspectRatio = function(set) {
return this.sumOfAspectRatio(set);
}
var linearPartition = function(set) {
var rows = Math.min(3, Math.ceil(set.length / 3));
var rowsArray = [];
for (var i = 0; i < rows; i++) {
rowsArray.push([]);
}
for (var l = 0; l < set.length; l++) {
var rowsAspectRatio = [];
for (var i = 0; i < rowsArray.length; i++) {
rowsAspectRatio.push(sumOfAspectRatio(rowsArray[i]));
}
var minIndex = 0, minValue = Infinity;
for (var i = 0; i < rowsArray.length; i++) {
if (rowsAspectRatio[i] < minValue) {
minValue = rowsAspectRatio[i];
minIndex = i;
}
}
rowsArray[minIndex].push(set[l]);
}
return rowsArray;
}
var setByRows = linearPartition(imagesList);
var rowsCount = setByRows.length;
var virtualHeight = Math.floor(divHeight / rowsCount);
for (var i = 0; i < setByRows.length; i++) {
var rowAspectRatio = sumOfAspectRatio(setByRows[i]);
var imagesCount = setByRows[i].length;
var virtualWidth = imagesCount > 1 ? divWidth - (imagesCount - 1) * padding : divWidth;
for (var l = 0; l < imagesCount; l++) {
var image = setByRows[i][l];
image.blockWidth = Math.floor(virtualWidth / imagesCount);
image.blockHeight = virtualHeight;
}
}
var resultList = [];
for (var i = 0; i < rowsCount; i++) {
var imagesCount = setByRows[i].length;
for (var l = 0; l < setByRows[i].length; l++) {
var image = setByRows[i][l];
image.marginBottom = rowsCount > 1 && i < rowsCount - 1 ? padding : 0;
image.marginRight = imagesCount > 1 && l < imagesCount - 1 ? padding : 0;
resultList.push(image);
}
}
$scope.imageList = resultList;
});
Обновление 01.09.2016 16:50
По просьбе, поясню, что в моем понимании "не универсальное решение". Мой алгоритм не умеет разбивать картинки максимально эффективно.
Вот так отображается три картинки с помощью моего алгоритма:
Заметьте, у всех одинаковая ширина. Первая картинка (красная), обрезается очень
очень сильно, т.к. у нее самая большая ширина из всех картинок.
А вот так эти же картинки отображает ВК:
Не трудно заметить, что в этом варианте расположения отображается на много большая часть первой картинки (возможно, и всех остальных).
Именно из за этого я и считаю свой алгоритм "не универсальным".
Ответ 3
Посмотрите в сторону вот этих решений, не уверен что это именно то что вам нужно, но, думаю, может помочь в решении вашей проблемы.
rowGrid.js
Ещё скрипт, но уже с некоторым описанием(ENG)
Ответ 4
Все строки кроме первой имеют равную высоту. Сама высота значения не имеет, важн
пропорции, поэтому изначально можно взять любую. Для этой высоты перебрать число строк и количество картинок в них. Оставшиеся картинки отправить в первую строку. Вариант с наиболее равномерным расположением выбрать как результат.
Комментариев нет:
Отправить комментарий