Virtual gridding/resizing of image collage in PHP

I suggest you to create a grid and weight approach.

This answer is divided in 3 parts :

  1. Working with a virtual grid
  2. Randomly dispose image into that grid
  3. Implement transparency

Randomly dispose image into that grid

The approach is : we will shake all our images to get a random array, and dispose them randomly on lines : 1 to 4 images per line (you'll be able to change this value), and will use the higher image in each line to determine the height of each lines. So if an image is 50% higher than another, if they are not in the same line, you'll conserve proportions.

That's a bit harder, so I decided to never use graphics during the development of this part. That's why there's a lot of steps and debugs, but this helps a lot to get step by step to the final result.

We get the height of all of our images and the sum of them :

$images = array ();
$totalHeight = 0;
foreach (glob("images/*.jpg") as $jpg)
{
    $img = imagecreatefromjpeg($jpg);
    $images[$jpg] = imagesy($img);
    $totalHeight += $images[$jpg];
    imagedestroy($img);
}

echo "image list with heights:\n";
var_dump($images);
echo "total heights: {$totalHeight}\n";

Gives us :

image list with heights:
array(12) {
  ["images/image1.jpg"]=>
  int(392)
  ["images/image10.jpg"]=>
  int(640)
  ["images/image11.jpg"]=>
  int(364)
  ["images/image12.jpg"]=>
  int(324)
  ["images/image2.jpg"]=>
  int(533)
  ["images/image3.jpg"]=>
  int(360)
  ["images/image4.jpg"]=>
  int(768)
  ["images/image5.jpg"]=>
  int(330)
  ["images/image6.jpg"]=>
  int(360)
  ["images/image7.jpg"]=>
  int(338)
  ["images/image8.jpg"]=>
  int(600)
  ["images/image9.jpg"]=>
  int(391)
}
total heights: 5400

Then we shfuffle the image array to get a random disposition of images. We need to preserve keys, and shffle does not, so we need to trick a little.

// Shuffle image array of files preserving keys to get random image disposition
$keys = array_keys($images);
shuffle($keys);
$images = array_merge(array_flip($keys), $images);

// Separate image names and heights, will simplify our future work
$heights = array_values($images);
$images = array_keys($images);

echo "image list:\n";
var_dump($images);

echo "image heights:\n";
var_dump($heights);

Gives us :

image list:
array(12) {
  [0]=>
  string(17) "images/image6.jpg"
  [1]=>
  string(17) "images/image5.jpg"
  [2]=>
  string(18) "images/image10.jpg"
  [3]=>
  string(17) "images/image2.jpg"
  [4]=>
  string(18) "images/image12.jpg"
  [5]=>
  string(17) "images/image3.jpg"
  [6]=>
  string(17) "images/image4.jpg"
  [7]=>
  string(17) "images/image1.jpg"
  [8]=>
  string(17) "images/image8.jpg"
  [9]=>
  string(17) "images/image9.jpg"
  [10]=>
  string(18) "images/image11.jpg"
  [11]=>
  string(17) "images/image7.jpg"
}
image heights:
array(12) {
  [0]=>
  int(360)
  [1]=>
  int(330)
  [2]=>
  int(640)
  [3]=>
  int(533)
  [4]=>
  int(324)
  [5]=>
  int(360)
  [6]=>
  int(768)
  [7]=>
  int(392)
  [8]=>
  int(600)
  [9]=>
  int(391)
  [10]=>
  int(364)
  [11]=>
  int(338)
}

The important thing here is to check if we preserved the image / height association.

Now, we need to convert image heights to a percentage : so if you have 2 images, one is 50% higher than the second, so you'll have 66% of the total height for your first image, and 33% for the second one. This virtual height will be used as height on our grid.

$count = count($heights);
for ($i = 0; ($i < $count); $i++)
{
    $heights[$i] = ($heights[$i] * 100) / $totalHeight
}

echo "image heights in percents\n";
var_dump($heights);
echo "check : " . array_sum($heights) . " = 100\n";

Result in :

Image heights in percents
array(12) {
  [0]=>
  float(6.6666666666667)
  [1]=>
  float(6.1111111111111)
  [2]=>
  float(11.851851851852)
  [3]=>
  float(9.8703703703704)
  [4]=>
  int(6)
  [5]=>
  float(6.6666666666667)
  [6]=>
  float(14.222222222222)
  [7]=>
  float(7.2592592592593)
  [8]=>
  float(11.111111111111)
  [9]=>
  float(7.2407407407407)
  [10]=>
  float(6.7407407407407)
  [11]=>
  float(6.2592592592593)
}
check : 100 = 100

We now generate an array of lines, to see how many images we will put per lines. For this example, we want from 1 to 4 images per line. Change here the rand() % 4 + 1 by what you want to get as many images as you want per line. Example : rand() % 3 + 2 will give you between 2 and 5 images.

$lines = array ();
while ($count > 0)
{
    $nbImages = rand() % 4 + 1;
    if (($count - $nbImages) < 0)
    {
        $nbImages = $count;
    }

    $lines[] = $nbImages;
    $count -= $nbImages;
}

echo "Number of lines : " . count($lines) . "\n";
echo "images per line disposition :\n";
var_dump($lines);

Result in :

Number of lines : 5
images per line disposition :
array(5) {
  [0]=>
  int(3)
  [1]=>
  int(1)
  [2]=>
  int(1)
  [3]=>
  int(4)
  [4]=>
  int(3)
}

We need to associate an image with a line, and with a position in a line. This will give us help to get the position of our image in our grid.

$imageLines = array();
foreach ($lines as $key => $numberImg)
{
    while ($numberImg--)
    {
        $imageLines[] = $key;
    }
}

echo "image / line association:\n";
var_dump($imageLines);

$imagePositions = array();
foreach ($lines as $numberImg)
{
    for ($i = 0; ($i < $numberImg); $i++)
    {
        $imagePositions[] = $i;
    }
}

echo "image / position in a line association:\n";
var_dump($imagePositions);

Result in :

image / line association:
array(12) {
  [0]=>
  int(0)
  [1]=>
  int(0)
  [2]=>
  int(0)
  [3]=>
  int(1)
  [4]=>
  int(2)
  [5]=>
  int(3)
  [6]=>
  int(3)
  [7]=>
  int(3)
  [8]=>
  int(3)
  [9]=>
  int(4)
  [10]=>
  int(4)
  [11]=>
  int(4)
}
image / position in a line association:
array(12) {
  [0]=>
  int(0)
  [1]=>
  int(1)
  [2]=>
  int(2)
  [3]=>
  int(0)
  [4]=>
  int(0)
  [5]=>
  int(0)
  [6]=>
  int(1)
  [7]=>
  int(2)
  [8]=>
  int(3)
  [9]=>
  int(0)
  [10]=>
  int(1)
  [11]=>
  int(2)
}

Now, we need to get total width of our image. We have from 1 to 4 images, so to get integer size of each images regardless to the number of images per line we have, we multiply 4 (the maximum values) by all values from 4 to 1. In this case: 4 * 3 * 2 * 1 = 24, so if we have 1 image/line, its width will be 24, 2 images/line: 24/2 = 12, 3 images/line: 24/3=8, 4 images/line: 24/4=6. All are valid integers.

$i = 4;
$virtualWidth = 1;
while ($i)
{
    $virtualWidth *= $i--;
}

echo "virtual width: {$virtualWidth}\n";

Nothing difficult here, this results in :

virtual width: 24

We need now to determine the height of each line, according to the highest image per line. We can also deduce our grid height in this calculation.

// Determine the virtual height needed for each line and for the whole grid
$imageHeights = array();
$index = 0;
foreach ($lines as $key => $numberImages)
{
    $slice = array_slice($heights, $index, $numberImages);

    echo "at line {$key}, images heights are:\n";
    var_dump($slice);

    $imageHeights[] = max($slice);
    $index += $numberImages;
}
$virtualHeight = array_sum($imageHeights);

echo "heights for each line:\n";
var_dump($imageHeights);
echo "total height = {$virtualHeight}\n";

This results in :

at line 0, images heights are:
array(3) {
  [0]=>
  float(6.6666666666667)
  [1]=>
  float(6.1111111111111)
  [2]=>
  float(11.851851851852)
}
at line 1, images heights are:
array(1) {
  [0]=>
  float(9.8703703703704)
}
at line 2, images heights are:
array(1) {
  [0]=>
  int(6)
}
at line 3, images heights are:
array(4) {
  [0]=>
  float(6.6666666666667)
  [1]=>
  float(14.222222222222)
  [2]=>
  float(7.2592592592593)
  [3]=>
  float(11.111111111111)
}
at line 4, images heights are:
array(3) {
  [0]=>
  float(7.2407407407407)
  [1]=>
  float(6.7407407407407)
  [2]=>
  float(6.2592592592593)
}
heights for each line:
array(5) {
  [0]=>
  float(11.851851851852)
  [1]=>
  float(9.8703703703704)
  [2]=>
  int(6)
  [3]=>
  float(14.222222222222)
  [4]=>
  float(7.2407407407407)
}
total height = 49.185185185185

We check in this result if we have the highest value for each line, and if the sum is valid.

We finally have all information we need to display our grid of randomly positionned images.

$imageGrid = new imageGrid(800, 800, $virtualWidth, $virtualHeight);
foreach (glob("images/*.jpg") as $jpg)
{
    $img = imagecreatefromjpeg($jpg);

    $index = array_search($jpg, $images);
    echo "image {$index}:\n";

    $line = $imageLines[$index];
    echo "image is at line {$line}\n";

    $sizeW = ($virtualWidth / $lines[$line]);
    echo "width = {$virtualWidth} / {$lines[$line]} = {$sizeW}\n";

    $sizeH = $imageHeights[$line];
    echo "height = {$imageHeights[$line]}\n";

    $posX = $imagePositions[$index] * ($virtualWidth / $lines[$line]);
    echo "pos X = {$imagePositions[$index]} * ({$virtualWidth} / {$lines[$line]}) = {$posX}\n";

    $slice = array_slice($imageHeights, 0, $line);
    echo "Slice to calc Y:\n";
    var_dump($slice);

    $posY = array_sum($slice);
    echo "pos Y = {$posY}\n";

    echo "\n";

    $imageGrid->putImage($img, $sizeW, $sizeH, $posX, $posY);
    imagedestroy($img);
}

This results in :

image 7:
image is at line 3
width = 24 / 4 = 6
height = 14.222222222222
pos X = 2 * (24 / 4) = 12
Slice to calc Y:
array(3) {
  [0]=>
  float(11.851851851852)
  [1]=>
  float(9.8703703703704)
  [2]=>
  int(6)
}
pos Y = 27.722222222222

image 2:
image is at line 0
width = 24 / 3 = 8
height = 11.851851851852
pos X = 2 * (24 / 3) = 16
Slice to calc Y:
array(0) {
}
pos Y = 0

image 10:
image is at line 4
width = 24 / 3 = 8
height = 7.2407407407407
pos X = 1 * (24 / 3) = 8
Slice to calc Y:
array(4) {
  [0]=>
  float(11.851851851852)
  [1]=>
  float(9.8703703703704)
  [2]=>
  int(6)
  [3]=>
  float(14.222222222222)
}
pos Y = 41.944444444444

image 4:
image is at line 2
width = 24 / 1 = 24
height = 6
pos X = 0 * (24 / 1) = 0
Slice to calc Y:
array(2) {
  [0]=>
  float(11.851851851852)
  [1]=>
  float(9.8703703703704)
}
pos Y = 21.722222222222

image 3:
image is at line 1
width = 24 / 1 = 24
height = 9.8703703703704
pos X = 0 * (24 / 1) = 0
Slice to calc Y:
array(1) {
  [0]=>
  float(11.851851851852)
}
pos Y = 11.851851851852

image 5:
image is at line 3
width = 24 / 4 = 6
height = 14.222222222222
pos X = 0 * (24 / 4) = 0
Slice to calc Y:
array(3) {
  [0]=>
  float(11.851851851852)
  [1]=>
  float(9.8703703703704)
  [2]=>
  int(6)
}
pos Y = 27.722222222222

image 6:
image is at line 3
width = 24 / 4 = 6
height = 14.222222222222
pos X = 1 * (24 / 4) = 6
Slice to calc Y:
array(3) {
  [0]=>
  float(11.851851851852)
  [1]=>
  float(9.8703703703704)
  [2]=>
  int(6)
}
pos Y = 27.722222222222

image 1:
image is at line 0
width = 24 / 3 = 8
height = 11.851851851852
pos X = 1 * (24 / 3) = 8
Slice to calc Y:
array(0) {
}
pos Y = 0

image 0:
image is at line 0
width = 24 / 3 = 8
height = 11.851851851852
pos X = 0 * (24 / 3) = 0
Slice to calc Y:
array(0) {
}
pos Y = 0

image 11:
image is at line 4
width = 24 / 3 = 8
height = 7.2407407407407
pos X = 2 * (24 / 3) = 16
Slice to calc Y:
array(4) {
  [0]=>
  float(11.851851851852)
  [1]=>
  float(9.8703703703704)
  [2]=>
  int(6)
  [3]=>
  float(14.222222222222)
}
pos Y = 41.944444444444

image 8:
image is at line 3
width = 24 / 4 = 6
height = 14.222222222222
pos X = 3 * (24 / 4) = 18
Slice to calc Y:
array(3) {
  [0]=>
  float(11.851851851852)
  [1]=>
  float(9.8703703703704)
  [2]=>
  int(6)
}
pos Y = 27.722222222222

image 9:
image is at line 4
width = 24 / 3 = 8
height = 7.2407407407407
pos X = 0 * (24 / 3) = 0
Slice to calc Y:
array(4) {
  [0]=>
  float(11.851851851852)
  [1]=>
  float(9.8703703703704)
  [2]=>
  int(6)
  [3]=>
  float(14.222222222222)
}
pos Y = 41.944444444444

To debug our code, we end it with :

$debug = true;
if ($debug)
{
    echo ob_get_clean();
}
else
{
    ob_clean();
    $imageGrid->display();
}

Okey, you have all the steps here. What about the result ?

enter image description here

F5...

enter image description here

F5...

enter image description here

Final code :

ob_start();

echo '<pre>';

// Get height of all images
$images = array ();
$totalHeight = 0;
foreach (glob("images/*.jpg") as $jpg)
{
    $img = imagecreatefromjpeg($jpg);
    $images[$jpg] = imagesy($img);
    $totalHeight += $images[$jpg];
    imagedestroy($img);
}

echo "image list with heights:\n";
var_dump($images);

// Shuffle image array of files preserving keys to get random image disposition
$keys = array_keys($images);
shuffle($keys);
$images = array_merge(array_flip($keys), $images);

// Separate image names and heights, will simplify our future work
$heights = array_values($images);
$images = array_keys($images);

echo "image list:\n";
var_dump($images);

echo "total heights: {$totalHeight}\n";

echo "image heights:\n";
var_dump($heights);

// Get percentage of image height compared to the total height
$count = count($heights);
for ($i = 0; ($i < $count); $i++)
{
    $heights[$i] = ($heights[$i] * 100) / $totalHeight; // becomes virtual height in a x100 grid
}

echo "image heights in percents\n";
var_dump($heights);
echo "check : " . array_sum($heights) . " = 100\n";

// Get random number of images per line and number of lines
// Between 1 to 4 images/line until there is no more image.
$lines = array ();
while ($count > 0)
{
    $nbImages = rand() % 4 + 1;
    if (($count - $nbImages) < 0)
    {
        $nbImages = $count;
    }

    $lines[] = $nbImages;
    $count -= $nbImages;
}

echo "Number of lines : " . count($lines) . "\n";
echo "images per line disposition :\n";
var_dump($lines);

// Associate an image with a line
$imageLines = array();
foreach ($lines as $key => $numberImg)
{
    while ($numberImg--)
    {
        $imageLines[] = $key;
    }
}

echo "image / line association:\n";
var_dump($imageLines);

// Associate an image with a position in a line
$imagePositions = array();
foreach ($lines as $numberImg)
{
    for ($i = 0; ($i < $numberImg); $i++)
    {
        $imagePositions[] = $i;
    }
}

echo "image / position in a line association:\n";
var_dump($imagePositions);

// We have from 1 to 4 images/line so we create a grid with a virtual width of 1*2*3*4.
// In this case, 1 image/line = 24, 2/line =24/2=12, 3/line=24/3=8, all are valid integers.
$i = 4;
$virtualWidth = 1;
while ($i)
{
    $virtualWidth *= $i--;
}

echo "virtual width: {$virtualWidth}\n";

// Determine the virtual height needed for each line and for the whole grid
$imageHeights = array();
$index = 0;
foreach ($lines as $key => $numberImages)
{
    $slice = array_slice($heights, $index, $numberImages);

    echo "at line {$key}, images heights are:\n";
    var_dump($slice);

    $imageHeights[] = max($slice);
    $index += $numberImages;
}
$virtualHeight = array_sum($imageHeights);

echo "heights for each line:\n";
var_dump($imageHeights);
echo "total height = {$virtualHeight}\n";


// Create a grid and place logically all images in the virtual area
$imageGrid = new imageGrid(800, 800, $virtualWidth, $virtualHeight);
foreach (glob("images/*.jpg") as $jpg)
{
    $img = imagecreatefromjpeg($jpg);

    // Determine position
    $index = array_search($jpg, $images);
    echo "image {$index}:\n";

    $line = $imageLines[$index];
    echo "image is at line {$line}\n";

    $sizeW = ($virtualWidth / $lines[$line]);
    echo "width = {$virtualWidth} / {$lines[$line]} = {$sizeW}\n";

    $sizeH = $imageHeights[$line];
    echo "height = {$imageHeights[$line]}\n";

    $posX = $imagePositions[$index] * ($virtualWidth / $lines[$line]);
    echo "pos X = {$imagePositions[$index]} * ({$virtualWidth} / {$lines[$line]}) = {$posX}\n";

    $slice = array_slice($imageHeights, 0, $line);
    echo "Slice to calc Y:\n";
    var_dump($slice);

    $posY = array_sum($slice);
    echo "pos Y = {$posY}\n";

    echo "\n";

    $imageGrid->putImage($img, $sizeW, $sizeH, $posX, $posY);
    imagedestroy($img);
}

$debug = false;
if ($debug)
{
    echo ob_get_clean();
}
else
{
    ob_clean();
    $imageGrid->display();
}

My approach is probably not the best one, but at least, it gives you a random positionning of your images into a grid preserving weight of images. The subject is quite difficult to handle so I hope this will be enough working for your case.


Implement transparency

First on the __construct method, replace :

    $white = imagecolorallocate($this->image, 255, 255, 255);
    imagefill($this->image, 0, 0, $white);

By :

    $transparent = imagecolorallocate($this->image, 255, 0, 255);
    imagefill($this->image, 0, 0, $transparent);
    imagecolortransparent($this->image, $transparent);

Then, on the resizePreservingAspectRatio method, add just after :

    $targetImg = imagecreatetruecolor($targetWidth, $targetHeight);

The following lines :

    $targetTransparent = imagecolorallocate($targetImg, 255, 0, 255);
    imagefill($targetImg, 0, 0, $targetTransparent);
    imagecolortransparent($targetImg, $targetTransparent);

And here we go.

enter image description here


Working with a virtual grid

Create a new image, with a real width/height (ex: 600x800), but also a grid width/height (ex: 10x10). Then you can give images a virtual size and virtual position. I'll try to make it step by step to make you understand what I mean.

First of all, we need an environment for this.

class imageGrid
{

    private $realWidth;
    private $realHeight;
    private $gridWidth;
    private $gridHeight;
    private $image;

    public function __construct($realWidth, $realHeight, $gridWidth, $gridHeight)
    {
        $this->realWidth = $realWidth;
        $this->realHeight = $realHeight;
        $this->gridWidth = $gridWidth;
        $this->gridHeight = $gridHeight;

        // create destination image
        $this->image = imagecreatetruecolor($realWidth, $realHeight);

        // set image default background
        $white = imagecolorallocate($this->image, 255, 255, 255);
        imagefill($this->image, 0, 0, $white);
    }

    public function __destruct()
    {
        imagedestroy($this->image);
    }

    public function display()
    {
        header("Content-type: image/png");
        imagepng($this->image);
    }

}

$imageGrid = new imageGrid(800, 600, 10, 10);
$imageGrid->display();

This will give us a beautiful white square. Then, we need a grid to display images. Because that's perhaps difficult to imagine, let's display it.

public function demoGrid()
{
    $black = imagecolorallocate($this->image, 0, 0, 0);
    imagesetthickness($this->image, 3);
    $cellWidth = ($this->realWidth - 1) / $this->gridWidth;   // note: -1 to avoid writting
    $cellHeight = ($this->realHeight - 1) / $this->gridHeight; // a pixel outside the image
    for ($x = 0; ($x <= $this->gridWidth); $x++)
    {
        for ($y = 0; ($y <= $this->gridHeight); $y++)
        {
            imageline($this->image, ($x * $cellWidth), 0, ($x * $cellWidth), $this->realHeight, $black);
            imageline($this->image, 0, ($y * $cellHeight), $this->realWidth, ($y * $cellHeight), $black);
        }
    }
}

By calling :

$imageGrid = new imageGrid(800, 600, 10, 10);
$imageGrid->demoGrid();
$imageGrid->display();

We can see :

enter image description here

Now, we want to know how to write a rectangle of 3x4, and paste it to (2,5) in our virtual measures. We need to search how to get real sizes and positions of our rectangle.

public function demoPutSquare($sizeW, $sizeH, $posX, $posY)
{
    // Cell width
    $cellWidth = $this->realWidth / $this->gridWidth;
    $cellHeight = $this->realHeight / $this->gridHeight;

    // Conversion of our virtual sizes/positions to real ones
    $realSizeW = ($cellWidth * $sizeW);
    $realSizeH = ($cellHeight * $sizeH);
    $realPosX = ($cellWidth * $posX);
    $realPosY = ($cellHeight * $posY);

    // Getting top left and bottom right of our rectangle
    $topLeftX = $realPosX;
    $topLeftY = $realPosY;
    $bottomRightX = $realPosX + $realSizeW;
    $bottomRightY = $realPosY + $realSizeH;

    // Displaying rectangle
    $red = imagecolorallocate($this->image, 100, 0, 0);
    imagefilledrectangle($this->image, $topLeftX, $topLeftY, $bottomRightX, $bottomRightY, $red);
}

By calling :

$imageGrid = new imageGrid(800, 600, 10, 10);
$imageGrid->demoGrid();
$imageGrid->demoPutSquare(3, 4, 2, 5);
$imageGrid->display();

We get a square of 3x4 positionned at (2,5) in our grid :

enter image description here

Now let's get it more seriousely, we have good measures so we can paste an image.

public function putImage($img, $sizeW, $sizeH, $posX, $posY)
{
    // Cell width
    $cellWidth = $this->realWidth / $this->gridWidth;
    $cellHeight = $this->realHeight / $this->gridHeight;

    // Conversion of our virtual sizes/positions to real ones
    $realSizeW = ceil($cellWidth * $sizeW);
    $realSizeH = ceil($cellHeight * $sizeH);
    $realPosX = ($cellWidth * $posX);
    $realPosY = ($cellHeight * $posY);

    // Copying the image
    imagecopyresampled($this->image, $img, $realPosX, $realPosY, 0, 0, $realSizeW, $realSizeH, imagesx($img), imagesy($img));
}

By calling :

$imageGrid = new imageGrid(800, 600, 10, 10);
$imageGrid->demoGrid();
$img = imagecreatefromjpeg("ninsuo.jpg");
$imageGrid->putImage($img, 3, 4, 2, 5);
$imageGrid->display();

We get :

enter image description here

That way we have a picture in the good place, but we lost the aspect ratio. Let's add a method to resize correctly our image.

public function resizePreservingAspectRatio($img, $targetWidth, $targetHeight)
{
    $srcWidth = imagesx($img);
    $srcHeight = imagesy($img);

    $srcRatio = $srcWidth / $srcHeight;
    $targetRatio = $targetWidth / $targetHeight;
    if (($srcWidth <= $targetWidth) && ($srcHeight <= $targetHeight))
    {
        $imgTargetWidth = $srcWidth;
        $imgTargetHeight = $srcHeight;
    }
    else if ($targetRatio > $srcRatio)
    {
        $imgTargetWidth = (int) ($targetHeight * $srcRatio);
        $imgTargetHeight = $targetHeight;
    }
    else
    {
        $imgTargetWidth = $targetWidth;
        $imgTargetHeight = (int) ($targetWidth / $srcRatio);
    }

    $targetImg = imagecreatetruecolor($targetWidth, $targetHeight);

    imagecopyresampled(
       $targetImg,
       $img,
       ($targetWidth - $imgTargetWidth) / 2, // centered
       ($targetHeight - $imgTargetHeight) / 2, // centered
       0,
       0,
       $imgTargetWidth,
       $imgTargetHeight,
       $srcWidth,
       $srcHeight
    );

    return $targetImg;
}

And just before :

    imagecopyresampled($this->image, $img, $realPosX, $realPosY, 0, 0, $realSizeW, $realSizeH, imagesx($img), imagesy($img));

We put :

    $img = $this->resizePreservingAspectRatio($img, $realSizeW, $realSizeH);

This look like this :

enter image description here

We now have a full functionnal class to make your job.

class imageGrid
{

    private $realWidth;
    private $realHeight;
    private $gridWidth;
    private $gridHeight;
    private $image;

    public function __construct($realWidth, $realHeight, $gridWidth, $gridHeight)
    {
        $this->realWidth = $realWidth;
        $this->realHeight = $realHeight;
        $this->gridWidth = $gridWidth;
        $this->gridHeight = $gridHeight;

        // create destination image
        $this->image = imagecreatetruecolor($realWidth, $realHeight);
        $black = imagecolorallocate($this->image, 0, 0, 0);
        imagecolortransparent($this->image, $black);
    }

    public function __destruct()
    {
        imagedestroy($this->image);
    }

    public function display()
    {
        header("Content-type: image/png");
        imagepng($this->image);
    }

    public function putImage($img, $sizeW, $sizeH, $posX, $posY)
    {
        // Cell width
        $cellWidth = $this->realWidth / $this->gridWidth;
        $cellHeight = $this->realHeight / $this->gridHeight;

        // Conversion of our virtual sizes/positions to real ones
        $realSizeW = ceil($cellWidth * $sizeW);
        $realSizeH = ceil($cellHeight * $sizeH);
        $realPosX = ($cellWidth * $posX);
        $realPosY = ($cellHeight * $posY);

        $img = $this->resizePreservingAspectRatio($img, $realSizeW, $realSizeH);

        // Copying the image
        imagecopyresampled($this->image, $img, $realPosX, $realPosY, 0, 0, $realSizeW, $realSizeH, imagesx($img), imagesy($img));
    }

    public function resizePreservingAspectRatio($img, $targetWidth, $targetHeight)
    {
        $srcWidth = imagesx($img);
        $srcHeight = imagesy($img);

        $srcRatio = $srcWidth / $srcHeight;
        $targetRatio = $targetWidth / $targetHeight;
        if (($srcWidth <= $targetWidth) && ($srcHeight <= $targetHeight))
        {
            $imgTargetWidth = $srcWidth;
            $imgTargetHeight = $srcHeight;
        }
        else if ($targetRatio > $srcRatio)
        {
            $imgTargetWidth = (int) ($targetHeight * $srcRatio);
            $imgTargetHeight = $targetHeight;
        }
        else
        {
            $imgTargetWidth = $targetWidth;
            $imgTargetHeight = (int) ($targetWidth / $srcRatio);
        }

        $targetImg = imagecreatetruecolor($targetWidth, $targetHeight);

        imagecopyresampled(
           $targetImg,
           $img,
           ($targetWidth - $imgTargetWidth) / 2, // centered
           ($targetHeight - $imgTargetHeight) / 2, // centered
           0,
           0,
           $imgTargetWidth,
           $imgTargetHeight,
           $srcWidth,
           $srcHeight
        );

        return $targetImg;
    }

}

We can now play with it to see if it works :

$imageGrid = new imageGrid(800, 400, 12, 2);

$blue = imagecreatefrompng("cheers_blue.png");
$imageGrid->putImage($blue, 6, 2, 0, 0);
imagedestroy($blue);

$green = imagecreatefrompng("cheers_green.png");
$imageGrid->putImage($green, 2, 1, 6, 0);
imagedestroy($green);

$red = imagecreatefrompng("cheers_red.png");
$imageGrid->putImage($red, 2, 1, 8, 0);
imagedestroy($red);

$yellow = imagecreatefrompng("cheers_yellow.png");
$imageGrid->putImage($yellow, 2, 1, 10, 0);
imagedestroy($yellow);

$purple = imagecreatefrompng("cheers_purple.png");
$imageGrid->putImage($purple, 3, 1, 6, 1);
imagedestroy($purple);

$cyan = imagecreatefrompng("cheers_cyan.png");
$imageGrid->putImage($cyan, 3, 1, 9, 1);
imagedestroy($cyan);

$imageGrid->display();

enter image description here

Personnally, I prefer the one without preserving aspect ratio :-)

enter image description here

Cheers! (eh, enjoy I would say!)