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.

25. July 2013 by
Categories: code, fancybox 2, html5, jquery, php, plugins | 34 comments

  • k1m1

    Thank you very much JFK! This was the one of the best tutorial I've seen on web!:)

  • Andrea

    JFK your tutorial is awesome... and thanks for that :-)
    i have a question: is it possible, with your script, to use FancyBox with the thumbnail helper option? i tried to make it work with that option but i can't... can you help me?

  • romy

    Hello, Thank you so much for this tutorial. That just what I was looking for !
    I have been carrefully following the instuctions provided, but something doesn't work...
    When I test my site on local with wamp, no images are loaded.. Just the category selector.

    What should I do? Please need your help..

    • JFK

      romy : Unfortunately I can't solve all individual issues. If I had a straight answer to your question I would give it to you but I don't. I just can advise you to review the steps in the tutorial and check your paths. Also you could use print_r($imgListArray); to verify/debug that the array is getting the images information. Any question regarding how-to php, javascript or jQuery, post it in the proper forums like stackoverflow (beware that you shouldn't ask how to implement a third-party tutorial but specific questions about code.) On the other hand, if you want to hire me to implement or troubleshoot this tutorial in your site, drop me a line using the contact form (contact us link on the left side of this page.) Thanks.

      • romy

        Thank you for taking the time to respond.
        A check found no errors on the code..
        I dunno what the problem is..

        Still working on...
        Soon

  • Andrea

    Hi JFK... me again :-P
    little question about your script: is it possible to exclude the "view all"?
    I'd like the php page show only the categories and once someone click on them the thumbs gallery appear.
    Unfortunately my level of PHP is too low for this.
    Thanks

    • JFK

      @andrea : I think it's possible to do what you want. You are free to customize the script to your needs. However, it would be impossible for me to help with every individual customization requests unless is a paid project. I hope you understand that. This post is meant to be a learning tool and reference and I am giving it away "as is".

      • Andrea

        You have been very kind
        so don't worry i understand
        This is an encouragement for me to learn PHP better

        Thanks again

  • dids

    Hi JFK, thanks for the tutorial its amazing stuff. I got this working fine and it looks really cool but I was wondering if this could be made to work with WordPress?

    I have used the same "/gallery/thumbs/" and "/gallery/*/*" structure as you have above but can this be used with the absolute paths in wordpress like:

    it returns:

    http://localhost/wordpress/wp-content/themes/theme-test/gallery/*/*

    as the location but I can't get it to even create the thumbs.

    the glob() function seems to return an empty array.

    Have you got any ideas?

    Many thanks

    • JFK

      if you have placed your gallery directory under your theme folder, don't forget to start with a period for a relative path like :./gallery/*/* otherwise starting with slash will look for the root of your WordPress path

  • http://mastropino.deviantart.com/ MastroPino

    Thanks mate, this help me a lot =)

  • Eelke

    If I use your code via de the download link and I also make a folder with several subfolders containing pictures, I do get the tags with my subfolder names in it, but I don't get to see any pictures. What am I doing wrong?

  • Eelke

    I have downloaded the file you made available here. Right now I am getting the following error: Notice: Use of undefined constant LinkTo - assumed 'LinkTo' in ...
    I have tried putting this constant in quotes. My images then appear, but fancy box does not work.
    Any ideas? Please ignore, my previous post that was my own mistake.

    • JFK

      @Eelke : I guess you have to compare your code carefully. It seems like you are having a syntax error while editing the code (a missing dot or comma?) You could add an error_reporting() ( http://www.php.net/manual/en/function.error-reporting.php ) function to your php code to identify the type of error you get.

  • Antonio

    Excellent work!

    I noticed that animated gifs are not correctly showed as thumbnails. I will try to fix it and let you know.... maybe you can make the third part of this tutorial :-)

    Thanks a lot!

    • JFK

      @Antonio : I am not sure I understood what animated gifs since thumbnails are created as jpg from the images in your folder.

      • Antonio

        I mean that thumbnails of animated gifs are not correctly created.

        I placed some animated gifs inside gallery/animated/ and the first time I run the web I have the following error:

        Warning: imagecreatefromjpeg(): './gallery/animated/aaa.gif' is not a valid JPEG file

        Of course, imagecreatefromjpeg do not supports gif as source, so I think that solution is quite simple: imagecreatefromgif()

        Same case for png format.

        Anyway, thank you very much for your great work with this gallery and your detailed explanations, it helped me a lot.

  • Lis

    Everything works great! Is it possible to add more than one filter to each image? I tried different methods, but can't figure it out. I appreciate your help.

    • JFK

      @Lis : It might be possible, however bear in mind that images and filters are automatically created and set respectively, based on the existing directory structure (the idea is automatize the process rather than hard-code manually categories and filters.) I don't think I will cover that specific customization in this tutorial, sorry.

  • Ian

    My images dont slide to fit the space left by other images when selecting a filter tab. I have included all the jquery from fancybox and added your filter script.

    • JFK

      Ian, please analyze carefully the source code of the demo included in the tutorial to find out what you are doing differently. Your own settings and page configuration may differ and have an effect on your images.

  • Ian Snook

    I would like to have one of my categories set to active as opposed to view all. Is this possible?

    • JFK

      Yes I think it's possible. You are free to customize the script to your needs. However, it would be impossible for me to help with every individual customization requests unless is a paid project. I hope you understand that. This post is meant to be a learning tool and reference and I am giving it away "as is".

    • JFK

      Maybe my answer to Tejus would help

  • JFK

    I think it would be possible. I guess you could do in your jQuery script (after the $(".filter").on("click") binding event) something like :

    if (window.location.hash) {
    var thisFilter = window.location.hash.split("#")[1].toLowerCase().substring(0, 4);
    $(".filter").each(function (i) {
    if ($(this).data("rel") == thisFilter) {
    $(".filter").eq(i).trigger("click");
    }
    });
    }

    DEMO : http://www.picssel.com/demos/fboxfilteredgalleryWithHash.html#Landscapes

  • ma3743

    hi
    thank you for this tutorial
    but what about big gallery (for example 1000 images)? it should load 1000 tumbnail image at first load
    is this true?

    • JFK

      It should, however it depends on your server's PHP settings. I have seen cases where you need to reload the page several times when trying to create the thumbnails for the first time. Notice that once the thumbs are crated, the script won't try to re-create them again unless you added new images to the folder. In that case the script will only create the thumbnails of those newly added images.

  • jepu

    Hi, can you show how to add image name just below the image.

  • JulienKisign

    Hello,
    is it possible to assign multiple filters to a photo ?
    Thank you .

  • Scott

    I don't see, in the demo code, that you are iterating lower than the ./gallery/* level. My deployment is only getting the ./gallery/* level, not ./gallery/*/* Is this how the demo is intended?

  • Nic_Jay

    Is it possible to attribute multiple categories to a single image? For instance assigning both "Animals" and "Architecture" to a one image? -- Thank you, great tutorial