5

I need to distribute "n" amount of images in a given container. It should optimize the space depending if the container's aspect ratio is landscape, portrait or a square. The intention is that the images are rendered the biggest size possible and all of them have the same space available. For this, I plan to create a grid but I need to know how many columns and how many rows it must have according to the aspect ratio of the container.

I took a look at this question but it is not exactly what I need.

This image with n = 8 should clarify a bit:

Calculate columns and rows

If the container is vertical, then 4 rows and 2 columns are needed, if the container is a square, then 3 rows and 3 columns are needed, if the container is horizontal, then 2 rows and 4 columns are needed.

I am writing a function but I am stuck in the middle:

private int[] calculateRowsAndColumnsNeeded(int numberOfImages, Dimension containerSize){
int numberOfColumns = 0;
int numberOfRows = 0;

int containerArea = containerSize.height * containerSize.width;
float singleCellArea = containerArea / numberOfImages;
double cellSideLength = Math.sqrt(singleCellArea);

// What to do with cellSideLength to get the right number of columns and rows?

return new int[]{numberOfColumns, numberOfRows};}

I would really appreciate some help here.

Thanks in advance,

Diego

Community
  • 1
  • 1
DiegoSahagun
  • 730
  • 1
  • 11
  • 22
  • Why reinvent the wheel? Just use setPreferredSize() on the image components (to make them all the same size) and leave the layout job to a LayoutManager (e.g. GridLayout)? – Durandal Jan 22 '14 at 15:32
  • I think for GridLayout you need to specify the number of rows and columns, otherwise it "Creates a grid layout with a default of one column per component, in a single row." – DiegoSahagun Jan 22 '14 at 16:01
  • You have to specify the number of rows OR the number of columns (either of them may be 0, and will then be chosen automatically according to the other one and the actual number of components that are added) - not sure whether this helps you, though... I once wrote a layout that MIGHT be close to what you want: It computed the number of rows/cols that are needed in order to minimize the space that is wasted for given aspect ratios of the cells and the container - is this what you are looking for? – Marco13 Jan 22 '14 at 20:44
  • @Marco13 "either of them may be 0", That's right, but you still need to know one of the values :) – DiegoSahagun Jan 24 '14 at 12:26

3 Answers3

3

I came to a solution, it might be not the best algorithm but it works, at least, for 1 - 20 elements which is what I need. I didn't test further. I will improve it later if I find a way.

    private static int[] calculateRowsAndColumnsNeeded(int numberOfImages, Dimension containerSize){
    int colsAttempt = 0;
    int rowsAttempt = 0;
    // Calculate the length of one side from a single cell
    int containerArea = containerSize.height * containerSize.width;
    float singleCellArea = containerArea / numberOfImages;
    double cellSideLength = Math.sqrt(singleCellArea);

    colsAttempt = (int) Math.floor(containerSize.width / cellSideLength);
    rowsAttempt =  (int) Math.floor(containerSize.height / cellSideLength);

    if (colsAttempt * rowsAttempt >= numberOfImages){

        return new int[]{rowsAttempt, colsAttempt};

    }
    // If the container is a square or bigger horizontally than vertically
    else if (containerSize.height <= containerSize.width){

        colsAttempt = (int) Math.ceil(containerSize.width / cellSideLength);
        rowsAttempt =  (int) Math.floor(containerSize.height / cellSideLength);

        if (colsAttempt * rowsAttempt >= numberOfImages){
            // 
            return new int[]{rowsAttempt, colsAttempt};

        }else{

            colsAttempt = (int) Math.floor(containerSize.width / cellSideLength);
            rowsAttempt =  (int) Math.ceil(containerSize.height / cellSideLength);

            if (colsAttempt * rowsAttempt >= numberOfImages){
                return new int[]{rowsAttempt, colsAttempt};
            }else{
                colsAttempt = (int) Math.ceil(containerSize.width / cellSideLength);
                rowsAttempt =  (int) Math.ceil(containerSize.height / cellSideLength);

                if (colsAttempt * rowsAttempt >= numberOfImages){
                    return new int[]{rowsAttempt, colsAttempt};
                }else{
                    return null;
                }
            }
        }
    } 
    // If the container is bigger vertically than horizontally
    else {

        colsAttempt = (int) Math.floor(containerSize.width / cellSideLength);
        rowsAttempt =  (int) Math.ceil(containerSize.height / cellSideLength);

        if (colsAttempt * rowsAttempt >= numberOfImages){
            // 
            return new int[]{rowsAttempt, colsAttempt};

        }else{

            colsAttempt = (int) Math.ceil(containerSize.width / cellSideLength);
            rowsAttempt =  (int) Math.floor(containerSize.height / cellSideLength);

            if (colsAttempt * rowsAttempt >= numberOfImages){
                return new int[]{rowsAttempt, colsAttempt};
            }else{
                colsAttempt = (int) Math.ceil(containerSize.width / cellSideLength);
                rowsAttempt =  (int) Math.ceil(containerSize.height / cellSideLength);

                if (colsAttempt * rowsAttempt >= numberOfImages){
                    return new int[]{rowsAttempt, colsAttempt};
                }else{
                    return null;
                }
            }
        }
    }
}
DiegoSahagun
  • 730
  • 1
  • 11
  • 22
2

Here's something in JavaScript. Does this work for you?

var height = 30, //pixels
    width = 30,  //pixels
    n = 8,
    cellSideLength = Math.floor(Math.sqrt(height * width / n)),
    targetColumns = Math.floor(width / cellSideLength) == width / cellSideLength 
                  ? width/cellSideLength 
                  : Math.floor(width / cellSideLength) + 1,
    cellSideLengthTemp = cellSideLength 

targetColumns = Math.min(targetColumns,n)

while (width / cellSideLengthTemp < targetColumns)
    cellSideLengthTemp-- //decrease by one pixel

while (Math.floor(height / cellSideLengthTemp) * targetColumns < n)
    cellSideLengthTemp-- //decrease by one pixel

var numColumns = Math.floor(width / cellSideLengthTemp),
    numRows = 1

while (numColumns * numRows < n)
    numRows++

console.log(numColumns,numRows,cellSideLengthTemp,cellSideLength)
גלעד ברקן
  • 23,602
  • 3
  • 25
  • 61
  • Thanks for the code! I have just tried it and it decreases "cellSideLengthTemp" until it's zero, then it throws an arithmetic exception due to the division by 0. – DiegoSahagun Jan 22 '14 at 21:57
  • @DiegoSahagun Try my new edited version. If there's a problem, please let me know the values you gave `height`, `width`, and `n`. – גלעד ברקן Jan 22 '14 at 23:19
  • Hi, I appreciate a lot your help. I tested it again and this is what I get with my test: "To display 1 elements in a panel of 523 by 354 you need 1 rows and 2 columns." - While the right answer would be 1 row and 1 column, I am not sure what is the problem. Cheers! – DiegoSahagun Jan 24 '14 at 00:17
  • @DiegoSahagun Thanks for letting me know - I suppose one could add `targetColumns = Math.min(targetColumns,n)`. Anyway, nice to see you found your own solution. – גלעד ברקן Jan 24 '14 at 01:07
2

Assuming the cells are squares you could simply do this (pseudocode):

float proportion = width / height;
int columns = min(cellCount, (int)round(sqrt(proportion * cellCount)));
int rows = (cellCount + columns - 1) / columns;  // int division with rounding up
DrummerB
  • 39,814
  • 12
  • 105
  • 142