Create a filtered image gallery with jQuery and Fancybox – Part 2 : automatically create image thumbnails with php

In Part 1 we learned how to build a filtered image gallery with jQuery and Fancybox.

Now we are going to learn how to generate our image gallery from images stored in different folders or sub-directories and also create their corresponding thumbnails automatically using php.

With this method, adding images to our existing gallery would be as easy as just placing them in their corresponding category folder.

filtered image gallery - php
The context

In Part 1, this is what we did to create our gallery :

  1. Separated our images by their corresponding category
  2. Used our favourite image editor to edit, resize and crop each image and create its image thumbnail (100x100 pixels as in the demo)
  3. Placed/saved those thumbnails in a separated folder
  4. Coded each link with its corresponding image path, thumbnail and category
  5. Optionally, re-arranged those links to mix the categories rather than showing them sequentially (so the filtering functionality makes more sense)

For a gallery of 10 items, that doesn't sound like too much work, but what if we want to create a gallery of 100 items?

Also, take in consideration that we would need to repeat the steps above for each new image we may want to add to our existing gallery. Now it really sounds like a lot of work, doesn't it?

The folder structure

The first step (as in Part 1) is to organize our gallery by different categories :

  • Animals
  • Landscapes
  • Architecture

Based on the category organization above, we would need to create a similar folder structure for our images like :

gallery folder structure - Fig 01

Notice that we also have a thumbs folder where we will save the automatically generated thumbnails.

IMPORTANT : You can have as many folders or sub-directories as you need, however, for the effects of this tutorial, all category folders should be one-level depth only.
The basic html

Since php is going to generate most of the html code (the category selector tabs as well as the thumbnails), this is all what we need to start :

<!-- The category selector html-->
<div id="galleryTab">
  <a data-rel="all"  href="javascript:;" class="filter active">View all</a>
</div>

<!-- The thumbnails collection wrapper-->
<div class="galleryWrap"></div>
The php script

Before we start coding, let's make a list of the things our script should do :

  1. Get a list of all files under the "gallery" directory and its sub-directories
  2. Filter that list to include image files only
    (we may have other type of files like .ini, .dat, .log or system files that may have been added by other external processes)
  3. Create the image thumbnails from the original images and place them in the "thumbs" sub-directory
  4. Gather information regarding the location and the category each image belongs to
  5. Render the html thumbnails' links and the category tabs
IMPORTANT : This tutorial assumes that you are comfortable with php commands and syntax.
The loop

The loop is the script that will iterate through our directory structure and get a list of all the image files and place it in an array. To iterate through the directory structure the loop will use php's foreach and glob() functions :

<?php
// general variables
$imgListArray = array(); // main image array list 
$imgExtArray = array("jpg"); // accepted image extensions (in lower-case !important)
$thumbsDir = "./gallery/thumbs/"; // path to the thumbnails destination directory
$galleryFiles = "./gallery/*/*"; // path to all files and sub-directories (use your own gallery name directory)
// iterate all subdirectories and files 
foreach( glob( $galleryFiles ) as $file ) {
    $ext = strtolower( pathinfo( $file, PATHINFO_EXTENSION ) ); // get extension in lower-case for validation purposes
    $imagePath = pathinfo( $file, PATHINFO_DIRNAME ) . "/"; // get path for validation purposes (added trailing slash)
    // if image extension is valid (is in the $imgExtArray array) AND the image is not inside the "thumbs" sub-directory
    if( in_array( $ext, $imgExtArray ) && $imagePath != $thumbsDir ){
        // additional image variables 
        $imageName = pathinfo( $file, PATHINFO_BASENAME ); // returns "cheeta.jpg"
        $thumbnail = $thumbsDir.$imageName; // thumbnail full path and name, i.e "./gallery/thumbs/cheeta.jpg"
        $dataFilter = substr( $file, 10, 4 ); // from "./gallery/animals/cheeta.jpg" returns "anim" 
        // for each image, get width and height
        $imageSize = getimagesize( $file ); // image size 
        $imageWidth = $imageSize[0];  // extract image width 
        $imageHeight = $imageSize[1]; // extract image height
        // set the thumb size
        if( $imageHeight > $imageWidth ){
            // images is portrait so set thumbnail width to 100px and calculate height keeping aspect ratio
            $thumbWidth = 100;
            $thumbHeight = floor( $imageHeight * ( 100 / $imageWidth ) );           
            $thumbPosition  = "margin-top: -" . floor( ( $thumbHeight - 100 ) / 2 ) . "px; margin-left: 0";
        } else {
            // image is landscape so set thumbnail height to 100px and calculate width keeping aspect ratio
            $thumbHeight = 100;
            $thumbWidth = floor( $imageWidth * ( 100 / $imageHeight ) ); 
            $thumbPosition  = "margin-top: 0; margin-left: -" . floor( ( $thumbWidth - 100 ) / 2 ) . "px";
        } // END else if
        // verify if thumbnail exists, otherwise create it
        if ( !file_exists( $thumbnail ) ){
            $createFromjpeg = imagecreatefromjpeg( $file );
            $thumb_temp = imagecreatetruecolor( $thumbWidth, $thumbHeight );
            imagecopyresized( $thumb_temp, $createFromjpeg, 0, 0, 0, 0, $thumbWidth, $thumbHeight, $imageWidth, $imageHeight );
            imagejpeg( $thumb_temp, $thumbnail );
        } // END if()
        // Create sub-array for this image
        // notice the key,value pair
        $imgListSubArray = array( 
            LinkTo=>$file, 
            ImageName=> $imageName,
            Datafilter=>$dataFilter, 
            Thumbnail=>$thumbnail, 
            Position=>$thumbPosition
        );
        // Push this sub-array into main array $imgListArray
        array_push ( $imgListArray, $imgListSubArray ); 
    } // END if()
} // END foreach()
unset($file); // destroy the reference after foreach()
// END the loop
?>
The loop breakdown

The reason we want to place the list in the $imgListArrayvariable array is because our loop

foreach( glob( $galleryFiles ) as $file )

will return the list of files in alphabetical order as in the figure below :

  gallery
    a_folder
      aa_image
      ba_image
      ca_image
    b_folder
      ab_image
      bb_image
      cb_image
    c_folder
      ac_image
      bc_image
      cc_image

 
Since we want to re-arrange the list or sort it in a random order as in Part 1, it's easier to do it inside the array and before the html is rendered.

The image file type

In the array $imgExtArray we can select what type of image files we want to include in our gallery by their extension. Here we can add the image extensions we may want to include, i.e.

$imgExtArray = array("jpg","png","gif");

Notice that all image extensions are written in lower-case.
 

IMPORTANT : File names are case-sensitive so image.jpg, image.JPG and Image.Jpg will be treated as different files by php.

 
Since we don't want to set any possible CamelCase combination in our $imgExtArray array, we will convert all the extensions to lowercase via the $ext variable :

$ext = strtolower( pathinfo( $file, PATHINFO_EXTENSION ) );
Filling the gaps

For each image in our gallery, php needs to render an html like this :

<a href="./gallery/animals/cheeta.jpg" data-filter="anim" data-fancybox-group="gallery" class="fancybox imgContainer">
  <img src="./gallery/thumbs/cheeta.jpg" alt="image cheeta.jpg" style="margin-top: 0; margin-left: -17px" >
</a>
... etc.

so we need to get the information to fill the value of each attribute in the html structure above :

  1. The href : the path to the big image. We get this value from the $file variable in the loop
    foreach( glob( $galleryFiles ) as $file ) // $file returns ./gallery/animals/cheeta.jpg
  2. The data-filter : the first 4 letters of the sub-directory the image is located in. We get this value from the $dataFilter variable
    $dataFilter = substr( $file, 10, 4 ); //returns "anim" 
  3. The src of the img tag : the path to the thumbnail image. We build this value from the $thumbsDir, $imageName and $thumbnail variables
    $thumbsDir = "./gallery/thumbs/"; // the thumbs directory
    $imageName = pathinfo( $file, PATHINFO_BASENAME ); // returns "cheeta.jpg"
    $thumbnail = $thumbsDir.$imageName; // thumbnail full path and name "./gallery/thumbs/cheeta.jpg"
    
  4. The alt : the alternate image information. We get this value from the $imageName variable
    $imageName = pathinfo( $file, PATHINFO_BASENAME ); // returns "cheeta.jpg"
  5. The style : specific inline CSS rules to set the position of the thumbnail (to explain later). We get this value from the $thumbPosition variable.
    $thumbPosition  = "margin-top: n; margin-left: n;"

Notice that if you use a different name than "gallery", you may need to change the number of the starting position in the substr() function (Number 2) :

$dataFilter = substr($file, 10, 4); // ./gallery/a.. <==10th

In our example above, the first letter in animals has the 10th position in the string ./gallery/animals/cheeta.jpg (starting the count from 0)

However, if your gallery directory name is "picturegallery" for instance, then your $dataFilter variable should look like :

$dataFilter = substr($file, 17, 4); // ./picturegallery/a.. <==17th

because the first letter in animals has the 17th position in the
./picturegallery/animals/cheeta.jpg string.

Calculating the size of thumbnails

We want to create thumbnails that are 100x100px in size. However we need to take in consideration that some of our images may have a different orientation, either landscape or portrait,


 
or some can be larger than others,

so we can be dealing with images of all sort of sizes and orientation.

Because of that, first we need to get the actual image size if we want to keep the thumbnails' aspect ratio :

$imageSize = getimagesize( $file ); // image size
$imageWidth = $imageSize[0];  // extract image width
$imageHeight = $imageSize[1]; // extract image height

Second, we will set two rules depending on the image orientation :

  1. If the image has landscape orientation, the height of the thumbnail will always be 100px
  2. On the other hand, if the image has portrait orientation, the width of the thumbnail will always be 100px

The first rule will make sure that all landscape thumbnails will fill the 100px height regardless their width as in the image below :

landscape images 100px height

 
The second rule will make sure that all portrait thumbnails will fill the 100px width regardless their height as in the image below :

portrait images 100px height

Having set our rules and got the image size, then we can create our thumbnails (if they don't exist yet) and place them in their respective directory :

// set the thumb size
if( $imageHeight > $imageWidth ){
    // images is portrait so set thumbnail width to 100px and calculate height keeping aspect ratio
    $thumbWidth = 100;
    $thumbHeight = floor( $imageHeight * ( 100 / $imageWidth ) );           
    $thumbPosition  = "margin-top: -" . floor( ( $thumbHeight - 100 ) / 2 ) . "px; margin-left: 0";
} else {
    // image is landscape so set thumbnail height to 100px and calculate width keeping aspect ratio
    $thumbHeight = 100;
    $thumbWidth = floor( $imageWidth * ( 100 / $imageHeight ) ); 
    $thumbPosition  = "margin-top: 0; margin-left: -" . floor( ( $thumbWidth - 100 ) / 2 ) . "px";
} // END else if
// verify if thumbnail exists, otherwise create it
if ( !file_exists( $thumbnail ) ){
    $createFromjpeg = imagecreatefromjpeg( $file );
    $thumb_temp = imagecreatetruecolor( $thumbWidth, $thumbHeight );
    imagecopyresized( $thumb_temp, $createFromjpeg, 0, 0, 0, 0, $thumbWidth, $thumbHeight, $imageWidth, $imageHeight );
    imagejpeg( $thumb_temp, $thumbnail );
} // END if()
Notice that with the code above we are not creating actual thumbnails of 100x100px in size. We are creating all landscape thumbnails with a height of 100px regardless their width AND all portrait thumbnails with a width of 100px regardless their height.
The thumbnail window

Since we included the class="imgContainer" in our link ( see "Filling the Gaps" section ), we could set a CSS rule to create a square window of 100px for our thumbnails. In other words, to allow a partial view of the thumbnail as highlighted in red squares of the previous image above.

Here is the basic CSS :

.imgContainer {
  width: 100px;
  height: 100px;
  overflow: hidden; /* don't show anything outside the 100px square */
  display: block;
  float: left;
}

Notice that in our loop, we also used the $thumbPosition variable to set the position of the thumbnail (the style attribute in the img tag). That CSS inline rule will center the thumbnail in the visible 100px window as in the following image :

centered thumbs
NOTE : Ideally, styles should be applied using external CSS stylesheets, however since the position values in this case required some calculations, we set the position as inline CSS via php.
Save the array

We need to save the information for each image in a sub-array $imgListSubArray and push that sub-array into our main array $imgListArray. The purpose of this multidimensional (two-level) array is to randomly sort the gallery items as well as to save the information we need to "fill the gaps" while rendering the thumbnails :

// Create sub-array for each image
// notice the "key,value" pair
$imgListSubArray = array( 
    LinkTo=>$file, 
    ImageName=> $imageName,
    Datafilter=>$dataFilter, 
    Thumbnail=>$thumbnail, 
    Position=>$thumbPosition
);
// Push this sub-array into the main (multi-dimension) array $imgListArray
array_push ( $imgListArray, $imgListSubArray ); 
Rendering the thumbnails

We have created our thumbnails and placed them in the ./gallery/thumbs/ sub-directory. We also collected the information we needed and saved it in the $imgListArray array. Now we are ready to render the thumbnails in random order in our page.

We are going to do that inside our thumbnails' wrapper :

<!-- The thumbnails collection wrapper--> 
<div class="galleryWrap">
  <?php 
  // render thumbnails here
  ?>
</div>

The php script that renders the thumbnails needs to do :

  • randomly sort our $imgListArray array
  • count the elements inside $imgListArray
  • loop through the $imgListArray array and echo each item

We will use the php's shuffle(), count() and for() functions :

<?php 
// shuffle and render
shuffle( $imgListArray ); // random order otherwise is boring
$countedItems = count( $imgListArray ); // number of images in gallery
// render html links and thumbnails
for ( $row = 0; $row < $countedItems; $row++ ){
    // watch out for escaped double quotes 
    echo "<a class=\"fancybox imgContainer\" data-fancybox-group=\"gallery\" href=\"" .
          $imgListArray[$row][LinkTo] . "\" data-filter=\"" .
          $imgListArray[$row][Datafilter] . "\"><img src=\"" . 
          $imgListArray[$row][Thumbnail] . "\" style=\"" . 
          $imgListArray[$row][Position] . "\" alt=\"" . 
          $imgListArray[$row][ImageName] . "\" /></a>\n";          
} // END for()
?>
HINT : You can add additional styles to the .imgContainer selector like borders, CSS3 shadows, hover effects, etc.
The category selector

The category selector will allow us to filter the gallery by categories and browse a specific category in a Fancybox image gallery.

We will render our missing category tabs in our galleryTab container :

<!-- The category selector html-->
<div id="galleryTab">
  <a data-rel="all"  href="javascript:;" class="filter active">View all</a>
  <?php
      // render the missing category tabs
  ?>
</div>

We will set the categories iterating through the directory structure using php's foreach() and glob() functions. However, we are going to use a different flag to identify the directory names only :

$galleryDir = "./gallery/*"; // target directories under gallery : notice the star "*" after the trailing slash  
foreach( glob( $galleryDir, GLOB_ONLYDIR ) as $dir ) {
    // render category selector tabs and exclude the thumbnail directory
    if( $dir != "./gallery/thumbs" ){
        $dataRel = substr( $dir, 10, 4 ); // return first 4 letters of each folder as category
        $dirName = trim( substr( $dir, 10, 200 ) ); // returns a trimmed string (200 chars length) with name of folder without parent folder
        echo "<a data-rel=\"" . $dataRel . "\" href=\"javascript:;\" class=\"filter\">" . $dirName . "</a>"; 
    } // END if()
} // END foreach()
The demo

Finally, see it working
 
DEMO

Notice that every time you refresh the page, the thumbnails are re-ordered.

 
Also, you can see a real-world application of this tutorial at
http://www.castlehillcontracting.ca/gallery

Final notes

In the first version of this generated gallery I used php's RecursiveDirectoryIterator and RecursiveIteratorIterator classes to iterate through the directory structure of my gallery.

$dir = new RecursiveDirectoryIterator('./gallery/'); //select our images directory 
foreach (new RecursiveIteratorIterator($dir) as $filename => $file) {
    // the loop
}

I found that this method (arguable) used more allocated server memory though so I decided to use glob() instead.

Another reason is that both classes are available in php 5 only, while glob() is available since php v4.3.0+, which I think gives me more flexibility if php 5 is not an option.

It's up to you what to use and to run you own memory tests.

Download the file

For reference purposes, the complete documented php file is available at

GitHub

NOTE : The GitHub repository only includes the php source code but it doesn't include any image or css files (you should use your own)

 

Disclaimer

The images used in this tutorial belong to their respective authors and were used for demo purposes only. Source of the images http://openphoto.net/
 
Enjoy.