Изменение размера N # квадратов, чтобы они были как можно больше, но при этом вписывались в размер окна X по Y. (миниатюры!)

У меня есть N квадратов. У меня прямоугольная коробка. Я хочу, чтобы все квадраты поместились в коробке. Я хочу, чтобы квадраты были как можно больше.

Как вычислить наибольший размер квадратов, чтобы все они поместились в коробке?

Это для миниатюр в галерее миниатюр.

int function thumbnailSize(
    iItems, // The number of items to fit.
    iWidth, // The width of the container.
    iHeight, // The height of the container.
    iMin // The smallest an item can be.
)
{
    // if there are no items we don't care how big they are!    
    if (iItems = 0) return 0;

    // Max size is whichever dimension is smaller, height or width.
    iDimension = (iWidth min iHeight);

    // Add .49 so that we always round up, even if the square root
    // is something like 1.2.  If the square root is whole (1, 4, etc..)
    // then it won't round up.
    iSquare = (round(sqrt(iItems) + 0.49));

    // If we arrange our items in a square pattern we have the same
    // number of rows and columns, so we can just divide by the number
    // iSquare, because iSquare = iRows = iColumns.
    iSize = (iDimension / iSquare);

    // Don't use a size smaller than the minimum.
    iSize = (iSize max iMin);

    return iSize;
 }

Этот код в настоящее время работает нормально. Идея заключается в том, чтобы взять наименьший размер прямоугольного контейнера, представить, что контейнер является квадратом этого размера, а затем предположить, что у нас есть равное количество строк и столбцов, достаточное для размещения внутри квадратов iItems.

Эта функция отлично работает, если контейнер в основном квадратный. Однако, если у вас есть длинный прямоугольник, миниатюры получаются меньше, чем могли бы быть. Например, если мой прямоугольник имеет размер 100 x 300 и у меня есть три эскиза, он должен вернуть 100, но вместо этого возвращает 33.


person Jake    schedule 15.10.2009    source источник
comment
Язык не должен иметь значения, для меня это больше похоже на математический вопрос. Хотел бы я, чтобы у меня было время ответить прямо сейчас.   -  person Mark Ransom    schedule 16.10.2009
comment
Я использую язык Visual Dataflex. Однако это не похоже ни на что, что вы, вероятно, когда-либо видели, поэтому вместо этого я напечатал это. Я знаю, что он, вероятно, не будет компилироваться или работать на каком-либо языке, но стремился к чему-то близкому к PHP или C++/JS. Кроме того, Visual Dataflex ОЧЕНЬ многословен и плохо подходит для публикации коротких фрагментов.   -  person Jake    schedule 16.10.2009
comment
Как только вы узнаете ширину каждой миниатюры, вы можете рассчитать необходимое количество строк, а затем растянуть высоту, чтобы заполнить поле.   -  person Brandon Haugen    schedule 16.10.2009


Ответы (8)


Вероятно, не оптимально (если это работает, чего я не пробовал), но я думаю, что лучше, чем ваш текущий подход:

w: ширина прямоугольника

h: высота прямоугольника

n: количество изображений

a = w*h : площадь прямоугольника.

ia = a/n max площадь изображения в идеальном случае.

il = sqrt(ia) максимальная длина изображения в идеальном случае.

nw = round_up(w/il): количество изображений, которые нужно наложить друг на друга.

nh = round_up(h/il): количество изображений, которые нужно сложить рядом друг с другом.

l = min(w/nw, w/nh) : длина используемых изображений.

person Carsten    schedule 16.10.2009
comment
Быстрый просмотр в моей электронной таблице, и я считаю, что у нас есть победитель! Дайте мне одну минуту, чтобы добавить это в мою программу и протестировать. - person Jake; 16.10.2009
comment
Разве не так: l = min(w/nw, h/nh): длина используемых изображений. ? - person AllBecomesGood; 17.11.2017

Решение на https://math.stackexchange.com/a/466248 работает отлично.

Неоптимизированная реализация javascript:

var getMaxSizeOfSquaresInRect = function(n,w,h) 
{
    var sw, sh;
    var pw = Math.ceil(Math.sqrt(n*w/h));
    if (Math.floor(pw*h/w)*pw < n) sw = h/Math.ceil(pw*h/w);
    else sw = w/pw;
    var ph = Math.ceil(Math.sqrt(n*h/w));
    if (Math.floor(ph*w/h)*ph < n) sh = w/Math.ceil(w*ph/h);
    else sh = h/ph;
    return Math.max(sw,sh);
}
person Reinier    schedule 16.11.2017
comment
Да, этот лучше справляется с некоторыми соотношениями сторон, например. для ширины = 6, высоты = 4 и n = 2 принятый ответ возвращает квадрат со стороной 2, тогда как этот возвращает длину стороны 3. - person mgraham; 20.08.2020

Искал похожее решение, но вместо квадратов пришлось в контейнер впихнуть прямоугольники. Поскольку квадрат также является прямоугольником, мое решение также отвечает на этот вопрос.

Я объединил ответы из Neptilo и mcked в функцию fitToContainer(). Задайте количество прямоугольников, соответствующее n, containerWidth и containerHeight, а также исходным itemWidth и itemHeight. Если элементы не имеют исходной ширины и высоты, используйте itemWidth и itemHeight, чтобы указать желаемое соотношение элементов.

Например, fitToContainer(10, 1920, 1080, 16, 9) дает {nrows: 4, ncols: 3, itemWidth: 480, itemHeight: 270}, то есть четыре столбца и 3 строки размером 480 x 270 (пикселей или любой другой единицы измерения).

И чтобы уместить 10 квадратов в той же примерной области 1920x1080, вы можете вызвать fitToContainer(10, 1920, 1080, 1, 1), в результате чего получится {nrows: 2, ncols: 5, itemWidth: 384, itemHeight: 384}.

function fitToContainer(n, containerWidth, containerHeight, itemWidth, itemHeight) {
    // We're not necessarily dealing with squares but rectangles (itemWidth x itemHeight),
    // temporarily compensate the containerWidth to handle as rectangles
    containerWidth = containerWidth * itemHeight / itemWidth;
    // Compute number of rows and columns, and cell size
    var ratio = containerWidth / containerHeight;
    var ncols_float = Math.sqrt(n * ratio);
    var nrows_float = n / ncols_float;

    // Find best option filling the whole height
    var nrows1 = Math.ceil(nrows_float);
    var ncols1 = Math.ceil(n / nrows1);
    while (nrows1 * ratio < ncols1) {
        nrows1++;
        ncols1 = Math.ceil(n / nrows1);
    }
    var cell_size1 = containerHeight / nrows1;

    // Find best option filling the whole width
    var ncols2 = Math.ceil(ncols_float);
    var nrows2 = Math.ceil(n / ncols2);
    while (ncols2 < nrows2 * ratio) {
        ncols2++;
        nrows2 = Math.ceil(n / ncols2);
    }
    var cell_size2 = containerWidth / ncols2;

    // Find the best values
    var nrows, ncols, cell_size;
    if (cell_size1 < cell_size2) {
        nrows = nrows2;
        ncols = ncols2;
        cell_size = cell_size2;
    } else {
        nrows = nrows1;
        ncols = ncols1;
        cell_size = cell_size1;
    }

    // Undo compensation on width, to make squares into desired ratio
    itemWidth = cell_size * itemWidth / itemHeight;
    itemHeight = cell_size;
    return { nrows: nrows, ncols: ncols, itemWidth: itemWidth, itemHeight: itemHeight }
}

Форма реализации JavaScript mckeed дала мне лучшие результаты, чем другие ответы, которые я нашел. Идея сначала растянуть прямоугольник до квадрата пришла от Нептило.

person Jip    schedule 06.11.2019

вы хотите что-то более похожее на

n = количество миниатюр x = одна сторона прямоугольника y = другая сторона l = длина стороны миниатюры

l = sqrt((x * y)/n)

person Keith Nicholas    schedule 15.10.2009
comment
Только что провел быстрый эксперимент: прямоугольник 900 x 900 с 8 элементами. Длина = 318. 3 строки и 3 столбца означают, что вы имеете ширину 954 пикселя, что больше прямоугольника. - person Jake; 16.10.2009
comment
аааа, да, просто нужно масштабировать его до минимального подходящего масштаба, как показано на плакате ниже - person Keith Nicholas; 16.10.2009

Вот мой окончательный код, основанный на ответе unknown (google): Для парня, который хотел знать, на каком языке мой первый пост, это VisualDataflex:

Function ResizeThumbnails Integer iItems Integer iWidth Integer iHeight Returns Integer
    Integer iArea iIdealArea iIdealSize iRows iCols iSize  
    // If there are no items we don't care how big the thumbnails are!
    If (iItems = 0) Procedure_Return
    // Area of the container.
    Move (iWidth * iHeight) to iArea
    // Max area of an image in the ideal case (1 image).
    Move (iArea / iItems) to iIdealArea
    // Max size of an image in the ideal case.
    Move (sqrt(iIdealArea)) to iIdealSize
    // Number of rows.
    Move (round((iHeight / iIdealSize) + 0.50)) to iRows
    // Number of cols.
    Move (round((iWidth / iIdealSize) + 0.50)) to iCols
    // Optimal size of an image.
    Move ((iWidth / iCols) min (iHeight / iRows)) to iSize
    // Check to make sure it is at least the minimum.
    Move (iSize max iMinSize) to iSize
    // Return the size
    Function_Return iSize
End_Function
person Jake    schedule 16.10.2009

Это должно работать. Это решается алгоритмом, а не уравнением. Алгоритм следующий:

  • Охватите всю короткую сторону прямоугольников всеми квадратами.
  • Уменьшайте количество квадратов в этом отрезке (в результате увеличивая размер) до тех пор, пока глубина квадратов не превысит длинную сторону прямоугольника
  • Остановитесь, когда диапазон достигнет 1, потому что это лучшее, что мы можем получить.

Вот код, написанный на JavaScript:

function thumbnailSize(items, width, height, min) {

  var minSide = Math.min(width, height),
      maxSide = Math.max(width, height);

  // lets start by spanning the short side of the rectange
  // size: the size of the squares
  // span: the number of squares spanning the short side of the rectangle
  // stack: the number of rows of squares filling the rectangle
  // depth: the total depth of stack of squares
  var size = 0;
  for (var span = items, span > 0, span--) {
    var newSize = minSide / span;
    var stack = Math.ceil(items / span);
    var depth = stack * newSize; 
    if (depth < maxSide)
      size = newSize;
    else 
      break;
  }
  return Math.max(size, min);
}
person Stephen Delano    schedule 16.10.2009

В Objective C... длина стороны квадрата для заданного количества элементов в содержащем прямоугольнике.

int count = 8;    // number of items in containing rectangle
int width = 90;   // width of containing rectangle
int height = 50;  // width of container
float sideLength = 0; //side length to use.


float containerArea = width * height;
float maxArea = containerArea/count;
float maxSideLength = sqrtf(maxArea);  
float rows = ceilf(height/maxSideLength);   //round up
float columns = ceilf(width/maxSideLength); //round up

float minSideLength = MIN((width/columns), (height/rows));
float maxSideLength = MAX((width/columns), (height/rows));

// Use max side length unless this causes overlap 
if (((rows * maxSideLength) > height) && (((rows-1) * columns) < count) ||
    (((columns * maxSideLength) > width) && (((columns-1) * rows) < count))) {
    sideLength = minSideLength;
}
else {
    sideLength = maxSideLength;
}
person Andy Lister    schedule 14.04.2012

Моя реализация JavaScript:

var a = Math.floor(Math.sqrt(w * h / n));
return Math.floor(Math.min(w / Math.ceil(w / a), h / Math.ceil(h / a)));

Где w — ширина прямоугольника, h — высота, а n — количество квадратов, которые вы хотите втиснуть.

person Jakub Pelák    schedule 02.06.2016