Written by

Roberto Segura

Category:

Blog

16 October 2015

There is a common requirement when creating Joomla sites: generate images for com_content articles in different resolutions depending on where they are displayed.

Joomla has a `JImage` class that you can use to generate thumbnails. We will use it combined with a small trick that allows templates to use JLayout as a very powerful reusable layout system.

The article is based on com_content but the thumbnail generator is reusable. You can generate thumbs for any image from its URL + the desired resolution.

Our layout system will:

  • Automatically use image intro if present and fallbck to the full article image.
  • Create thumbnails for any resolution using a single layout with different parameters.
  • Force thumbnails to be created from a PNG image so we have transparent backgrounds.
  • Allow us to specify the thumbnail generation method.
  • Force thumbnail rewrite.

I will use the core article intro_image layout as example of how to call the layout system but the same code works anywhere where you have an article (views, modules, etc.).

Let's start creating an override of the layout that renders the article intro image. It is located in the folder:

layouts/joomla/content/intro_image.php

to create an override you have to copy it to your template folder. Our override will be in:

templates/{your_template}/html/layouts/joomla/content/intro_image.php

This is the default layout code (without the header):

$params  = $displayData->params;
?>
<?php $images = json_decode($displayData->images); ?>
<?php if (isset($images->image_intro) && !empty($images->image_intro)) : ?>
    <?php $imgfloat = (empty($images->float_intro)) ? $params->get('float_intro') : $images->float_intro; ?>
    <div class="pull-<?php echo htmlspecialchars($imgfloat); ?> item-image">
    <?php if ($params->get('link_titles') && $params->get('access-view')) : ?>
    <a href="/<?php echo JRoute::_(ContentHelperRoute::getArticleRoute($displayData->slug, $displayData->catid, $displayData->language)); ?>"><img
    <?php if ($images->image_intro_caption):
        echo 'class="caption"' . ' title="' . htmlspecialchars($images->image_intro_caption) . '"';
    endif; ?>
    src="<?php echo htmlspecialchars($images->image_intro); ?>" alt="<?php echo htmlspecialchars($images->image_intro_alt); ?>" itemprop="thumbnailUrl"/></a>
    <?php else : ?><img
    <?php if ($images->image_intro_caption):
        echo 'class="caption"' . ' title="' . htmlspecialchars($images->image_intro_caption) . '"';
    endif; ?>
    src="<?php echo htmlspecialchars($images->image_intro); ?>" alt="<?php echo htmlspecialchars($images->image_intro_alt); ?>" itemprop="thumbnailUrl"/>
    <?php endif; ?>
</div>
<?php endif; ?>

I think this is a good example of how we have to improve things for frontenders. Almost everything can be improved there!

If you can read that you will note that the layout receives an article as a parameter ($displayData) and from there it starts rendering the article intro image..

Let's clean that so we are able to understand better what are we doing. This is a summary of the changes done (they are explained also in the code):

  • I really hate $displayData so I'll rename it to $article because it describes the object we are handling.
  • A $params var is not required because we can access article params with $article->params->get('attribute').
  • I want to stop processing the layout as soon as requirements to render the image are not met. So I'll first check if we have an image and if not return.
  • I don't like PHP mixed mixed with HTML. It's better that you prepare any PHP var that you need before you start to write HTML.
  • I don't need caption at all so so I removed it

This is my cleaned layout:

// More descriptive name
$article = $displayData;

// Our article will only have a link if it's required so it's easier to check in our HTML
if ($article->params->get('link_titles') && $article->params->get('access-view'))
{
    $link = JRoute::_(ContentHelperRoute::getArticleRoute($article->slug, $article->catid, $article->language));
}
?>
<div class="item-image">
    <?php if ($link) : ?>
        <a href="/<?php echo $link; ?>" class="thumbnail">
    <?php endif; ?>
        <?php
            echo JLayoutHelper::render(
                'com_content.article.image',
                array(
                    'article'    => $article,
                    'options' => array(
                        'resolution' => '450x350'
                    )
                )
            )
        ?>
    <?php if ($link) : ?>
        </a>
    <?php endif; ?>
</div>

Hey that's better! (I hope you agree with me) but we aren't here to learn to clean layouts! Now is time to work on the thumbnail generation. Note that for some reason you will see / before href & src html attributes. If you want to use the code I'll post a link to a Gist at the end of this article which is the best way to get it.

Our new layout is a wrapper that will call another layout to render the article image. That makes our layouts more reusable because you can render only the image anywhere, with or without link, etc.

This is the article image layout called from our previous layout. It's stored in templates/{your_template}/html/layouts/com_content/article/image.php:

extract($displayData);

/**
 * Layout variables
 *
 * @var  stdClass   $article  Article as it comes from joomla models/helpers
 * @var  array   $options     Array with options. Available options:
 *                               'resolution'    : Resolution of the thumbnail
 *                               'force_png'     : Convert always to PNG for transparent backgrounds
 *                               'force_rewrite' : Force thumbnail regeneration
 *                               'thumb_method'  : Method to use to create the thumbnails. See JImage
 */

$layoutOptions = empty($options) ? new JInput : new JInput($options);
$images = json_decode($article->images);

// Intro image available
if (!empty($images->image_intro))
{
    $imageUrl = $images->image_intro;
    $imageAlt = $images->image_intro_alt;
}
// Full text image available
elseif (!empty($images->image_fulltext))
{
    $imageUrl = $images->image_fulltext;
    $imageAlt = $images->image_fulltext_alt;
}
// Nothing to display
else
{
    return;
}

$resolution  = $layoutOptions->get('resolution', '350x250');

// A thumbnail has been required
if ($resolution)
{
    $thumbUrl = JLayoutHelper::render(
        'image.thumbnail',
        array(
            'imageUrl' => $imageUrl,
            'options'  => $options
        )
    );
    if ($thumbUrl)
    {
        $imageUrl = $thumbUrl;
    }
}
?>
<img src="/<?php echo $imageUrl; ?>" alt="<?php echo $imageAlt; ?>" />

Let's explain it a bit:

  • First we extract the displayData var contents so all the properties of the array are converted to vars ready to be used. Available variables are detailed in the doc block.
  • We then use a JInput object to get sanitised data with default values support.
  • Next is to detect if we have to use the intro image or the full article image.
  • If a resolution is received call another layout that will be used in a tricky way: it will be our function to generate thumnails for our template.
  • If a thumb has been generated it will be used and if not relay in the original image.

And now the contents of the layout that will act as thumbnail generator for us. The layout path is:

templates/{your_template}/html/layouts/image/thumbnail.php

I've used a layout outside com_content because this layout is not specific for it.

extract($displayData);

/**
 * Layout variables
 *
 * @var  array   $imageUrl    Url of the source image
 * @var  array   $options     Array with options. Available options:
 *                               'resolution'   : Resolution of the thumbnail
 *                               'force_png'    : Convert always to PNG for transparent backgrounds
 *                               'force_rewrite' : Force thumbnail regeneration
 *                               'thumb_method' : Method to use to create the thumbnails. See JImage
 */

$layoutOptions = empty($options) ? new JInput : new JInput($options);

// Generate image path from its URL
$imagePath = JPATH_SITE . '/' . ltrim($imageUrl, '/');

// Cannot create the thumb
if (!file_exists($imagePath))
{
    echo $imageUrl;

    return;
}

// Layout options
$resolution   = $layoutOptions->get('resolution', '450x350');
$forcePng     = $layoutOptions->getBoolean('force_png', false);
$rewriteThumb = $layoutOptions->getBoolean('force_rewrite', false);
$thumbMethod  = $layoutOptions->getInt('thumb_method', JImage::SCALE_INSIDE);

$fileInfo =    pathinfo($imagePath);
$thumbPath = dirname($imagePath) . '/thumbs/' . $resolution . '/' . $fileInfo['filename'] . '.png';

// Thumb already exists
if (!$rewriteThumb && file_exists($thumbPath))
{
    echo str_replace(JPATH_SITE, JUri::root(true), $thumbPath);

    return;
}

// Source of the png file if we have to force PNG thumbnails
$pngPath = dirname($imagePath) . '/' . $fileInfo['filename'] . '.png';

if ($forcePng && $fileInfo['extension'] != 'png' && !file_exists($pngPath))
{
    $pngImage = imagecreatefromstring(file_get_contents($imagePath));
    imagepng($pngImage, $pngPath);
    $jimage = new JImage($pngPath);
}
else
{
    $jimage = new JImage($imagePath);
}

try
{
    $thumbs = $jimage->generateThumbs(array($resolution));
}
catch (Exception $e)
{
    $thumbs = array();
}

if (!$thumbs)
{
    echo $imageUrl;

    return;
}

// Try to create the thumb folder
if (!is_dir(dirname($thumbPath)) && !mkdir(dirname($thumbPath), 0755, true))
{
    echo $imageUrl;

    return;
}

$thumbs[0]->toFile($thumbPath, JImage::getImageFileProperties($jimage->getPath())->type);
echo str_replace(JPATH_SITE, JUri::root(true), $thumbPath);

Technically is not the best way to create a thumbnail (you should create your library for it) but it shows the power that JLayout has and this code can be used by anybody able to copy&paste code.

Note that your thumbnails will be generated in the same folder your image is stored in.

If your image is in:

/images/my-sample-image.jpg

A 450x350 thumbnail will be generated in:

/images/thumbs/450x350/my-sample-image.jpg (or png if it's forced)

I'll end this article with some examples of this layout system can be used to generate different thumbnails in different places of your site.

Create a 640x480 png thumbnail that is easier to style:

     <?php
            echo JLayoutHelper::render(
                'com_content.article.image',
                array(
                    'article'    => $article,
                    'options' => array(
                        'resolution' => '640x480',
                        'force_png'  => true
                    )
                )
            )
        ?>

Create a PNG thumbnail using JImage::SCALE_FIT as scale method. This will keep aspect ratio but add empty space so the thumb image has exactly the size requested:

<?php
    echo JLayoutHelper::render(
        'com_content.article.image',
        array(
            'article'    => $article,
            'options' => array(
                'resolution'   => '450x250',
                'force_png'    => true,
                'thumb_method' => JImage::SCALE_FIT
            )
        )
    )
?>

Now let's imagine that you want to use it in a mod_articles_latest module override to show article images. That module uses something like this to render the articles:

<ul class="latestnews<?php echo $moduleclass_sfx; ?>">
<?php foreach ($list as $item) :  ?>
    <li itemscope itemtype="http://schema.org/Article">
        <a href="/<?php echo $item->link; ?>" itemprop="url">
            <span itemprop="name">
                <?php echo $item->title; ?>
            </span>
        </a>
    </li>
<?php endforeach; ?>
</ul>

To connect our layout you only have to know that the var containig the article is $item. So we would call it like:

<ul class="latestnews<?php echo $moduleclass_sfx; ?>">
<?php foreach ($list as $item) :  ?>
    <li itemscope itemtype="http://schema.org/Article">
        <?php
            echo JLayoutHelper::render(
                'com_content.article.image',
                array(
                    'article'    => $item,
                    'options' => array(
                        'resolution'   => '250x350',
                        'force_png'    => true
                    )
                )
            )
        ?>
        <a href="/<?php echo $item->link; ?>" itemprop="url">
            <span itemprop="name">
                <?php echo $item->title; ?>
            </span>
        </a>
    </li>
<?php endforeach; ?>
</ul>

Easy to integrate. Isn't it? :)

You can find a Gist with the full code on Github