So Jip's answer above is by far the best one since it does an optimal fitting no matter the ratio between the aspect ratio of the container and the item, but it's JS and it's very unclear which are floats and ints in his code, and it's quite relevant to the endresult.
So here's a version of his code in C#:
public static (int nrows, int ncols, int itemWidth, int itemHeight) PackInContainer(int n, float containerWidth, float containerHeight, float itemAspectRatio)
{
// https://stackoverflow.com/questions/2476327/optimized-grid-for-rectangular-items
// We're not necessarily dealing with squares but rectangles (itemWidth x itemHeight),
// temporarily compensate the containerWidth to handle as rectangles
containerWidth = containerWidth * itemAspectRatio;
// Compute number of rows and columns, and cell size
float ratio = (float)containerWidth / (float)containerHeight;
float ncols_float = Mathf.Sqrt(n * ratio);
float nrows_float = n / ncols_float;
// Find best option filling the whole height
int nrows1 = Mathf.CeilToInt(nrows_float);
int ncols1 = Mathf.CeilToInt((float)n / (float)nrows1);
while (nrows1 * ratio < ncols1)
{
nrows1++;
ncols1 = Mathf.CeilToInt((float)n / (float)nrows1);
}
float cell_size1 = containerHeight / nrows1;
// Find best option filling the whole width
int ncols2 = Mathf.CeilToInt(ncols_float);
int nrows2 = Mathf.CeilToInt(n / ncols2);
while (ncols2 < nrows2 * ratio)
{
ncols2++;
nrows2 = Mathf.CeilToInt(n / ncols2);
}
float cell_size2 = containerWidth / ncols2;
// Find the best values
int nrows, ncols;
float 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
int itemWidth = Mathf.RoundToInt(cell_size * itemAspectRatio);
int itemHeight = Mathf.RoundToInt(cell_size);
return (nrows, ncols, itemWidth, itemHeight);
}