Get image dimensions with a plugin

Have a question or a suggestion about a 3rd party addon module or plugin?
Let us know here.
Locked
webform
Power Poster
Power Poster
Posts: 458
Joined: Sat Nov 25, 2006 3:39 pm
Location: Copenhagen, Denmark

Get image dimensions with a plugin

Post by webform »

I've used getimagesize($remoteimagefile) in a template and i works fine. But i've noticed that if i do have more than a couple of images the page slows down a lot.

I then tried to upload a plugin to "lib/plugins" but i can't seem to figure out how to access the output - I only get "Array"!

I have:

Code: Select all

{$imagesize = "{getjpegsize url=$my_url}"}
<pre>{$imagesize}</pre>
But it's only outputting "Array"!

I've also tried {$imagesize[0]} but then i only get "A".

So i'm clearly missing something but what?

My plugin file:

Code: Select all

// Retrieve JPEG width and height without downloading/reading entire image.
function smarty_cms_function_getjpegsize($params, &$smarty) {
    $img_loc = $params['url'];
    $handle = fopen($img_loc, "rb") or die("Invalid file stream.");
    $new_block = NULL;
    if(!feof($handle)) {
        $new_block = fread($handle, 32);
        $i = 0;
        if($new_block[$i]=="\xFF" && $new_block[$i+1]=="\xD8" && $new_block[$i+2]=="\xFF" && $new_block[$i+3]=="\xE0") {
            $i += 4;
            if($new_block[$i+2]=="\x4A" && $new_block[$i+3]=="\x46" && $new_block[$i+4]=="\x49" && $new_block[$i+5]=="\x46" && $new_block[$i+6]=="\x00") {
                // Read block size and skip ahead to begin cycling through blocks in search of SOF marker
                $block_size = unpack("H*", $new_block[$i] . $new_block[$i+1]);
                $block_size = hexdec($block_size[1]);
                while(!feof($handle)) {
                    $i += $block_size;
                    $new_block .= fread($handle, $block_size);
                    if($new_block[$i]=="\xFF") {
                        // New block detected, check for SOF marker
                        $sof_marker = array("\xC0", "\xC1", "\xC2", "\xC3", "\xC5", "\xC6", "\xC7", "\xC8", "\xC9", "\xCA", "\xCB", "\xCD", "\xCE", "\xCF");
                        if(in_array($new_block[$i+1], $sof_marker)) {
                            // SOF marker detected. Width and height information is contained in bytes 4-7 after this byte.
                            $size_data = $new_block[$i+2] . $new_block[$i+3] . $new_block[$i+4] . $new_block[$i+5] . $new_block[$i+6] . $new_block[$i+7] . $new_block[$i+8];
                            $unpacked = unpack("H*", $size_data);
                            $unpacked = $unpacked[1];
                            $height = hexdec($unpacked[6] . $unpacked[7] . $unpacked[8] . $unpacked[9]);
                            $width = hexdec($unpacked[10] . $unpacked[11] . $unpacked[12] . $unpacked[13]);
                            return array($width, $height);
                        } else {
                            // Skip block marker and read block size
                            $i += 2;
                            $block_size = unpack("H*", $new_block[$i] . $new_block[$i+1]);
                            $block_size = hexdec($block_size[1]);
                        }
                    } else {
                        return FALSE;
                    }
                }
            }
        }
    }
    return FALSE;
}
User avatar
Rolf
Dev Team Member
Dev Team Member
Posts: 7825
Joined: Wed Apr 23, 2008 7:53 am
Location: The Netherlands
Contact:

Re: Get image dimensions with a plugin

Post by Rolf »

- + - + - + - + - + - + -
LATEST TUTORIAL AT CMS CAN BE SIMPLE:
Migrating Company Directory module to LISE
- + - + - + - + - + - + -
Image
webform
Power Poster
Power Poster
Posts: 458
Joined: Sat Nov 25, 2006 3:39 pm
Location: Copenhagen, Denmark

Re: Get image dimensions with a plugin

Post by webform »

Thanks Rolf!

I've already tried that one but i'm fetching remote images and using getimagesize() seems to slow down the page considerably when fetching more than a handfull images.

So i found the above function that only get the image width and height from the remote image. But i'm stuck at getting the width value from the output.

If i do this:

Code: Select all

 {getjpegsize|print_r url=$my_url}
i get

Code: Select all

Array ( [0] => 1280 [1] => 720 ) 1
But if i do

Code: Select all

{$imagesize = "{getjpegsize url=$my_url}"}
{$imagesize[0]}
i get this output

Code: Select all

A
So how do i access the first value?
User avatar
Rolf
Dev Team Member
Dev Team Member
Posts: 7825
Joined: Wed Apr 23, 2008 7:53 am
Location: The Netherlands
Contact:

Re: Get image dimensions with a plugin

Post by Rolf »

I use this method i.e. in the PhotoSwipe template for the Gallery module:
http://dev.cmsmadesimple.org/project/fi ... ckage-1341

Example with more than 100 photos in this template at:
https://music4allharen.nl/fotoalbum/202 ... smaking/67
- + - + - + - + - + - + -
LATEST TUTORIAL AT CMS CAN BE SIMPLE:
Migrating Company Directory module to LISE
- + - + - + - + - + - + -
Image
webform
Power Poster
Power Poster
Posts: 458
Joined: Sat Nov 25, 2006 3:39 pm
Location: Copenhagen, Denmark

Re: Get image dimensions with a plugin

Post by webform »

But is the difference not in local and external image files?

I have 6 external image files and load time is ok. But do i increase the gallery to 12 images, then the page takes a long time to load (>10 sec.).

I'm trying to have a portfolio of YouTube videos, where i fetch the YouTube Poster Image depending which size is available (Either Maxresdefault or HQdefault).

And as far i can read on my searches around the web, is that getimagesize() is very slow with external image files, as it has to download the full image data to get width and height.

But maybe it's just in my template there is something wrong to increase load time so much?

My Portfolio template (In LISE):

Code: Select all

{if $items|@count > 0}{strip}
<!-- Portfolio -->
<div id="portfolio" class="grid-layout portfolio-3-columns" data-margin="0">
{foreach from=$items item=item name=portfolio}
    <!-- portfolio item -->
    <div class="portfolio-item{if $smarty.foreach.portfolio.first || $smarty.foreach.portfolio.iteration is div by 8} large-width{/if} img-zoom">
        <div class="portfolio-item-wrap">
            <div class="portfolio-image">
                <a data-lightbox="iframe" href="https://www.youtube.com/watch?v={$item->fielddefs.video_id.value}">
	                {if $item->fielddefs.poster_image.value}
	                <img src="{CGSmartImage src1=$item->fielddefs.poster_image->GetImagePath(true) src2=$item->fielddefs.poster_image->value filter_croptofit='800,450,c' noauto=1}" alt="">
		            {else}
		            {capture assign=maxresdefault}https://img.youtube.com/vi/{$item->fielddefs.video_id.value}/maxresdefault.jpg{/capture}
		            {capture assign=hqdefault}https://img.youtube.com/vi/{$item->fielddefs.video_id.value}/hqdefault.jpg{/capture}
		            {$imagesize = getimagesize($maxresdefault)}
		            {if $imagesize[0]}
		            {CGSmartImage src=$maxresdefault filter_croptofit='800,450,c' noauto=1}
		            {else}
		            {CGSmartImage src=$hqdefault filter_croptofit='480,270,c' noauto=1}
		            {/if}
		            {/if}
		         </a>
            </div>
            <div class="portfolio-description">
                <a class="btn btn-light btn-rounded" title="{$item->title} ({$imagesize[0]})" data-lightbox="iframe" href="https://www.youtube.com/watch?v={$item->fielddefs.video_id.value}">{$item->title}</a>
            </div>
        </div>
    </div>
    <!-- end: portfolio item -->  
{/foreach}
</div>
<!-- end: Portfolio -->
{/strip}{/if}
User avatar
Jo Morg
Dev Team Member
Dev Team Member
Posts: 1921
Joined: Mon Jan 29, 2007 4:47 pm

Re: Get image dimensions with a plugin

Post by Jo Morg »

webform wrote:{$imagesize = "{getjpegsize url=$my_url}"}
<pre>{$imagesize}</pre>

But it's only outputting "Array"!
Looking at the source code it easy to get what is wrong: return array($width, $height);
The typical use of a smarty plugin expects the return to be printable and an array is not printable.
There are several options to get around this, one being imploding the var before returning it, other being assigning the return values to one or two smarty variables and then handling the vars on the template.

Also a note: try NOT to use capture on the templates as that is time expensive and probably one of the issues you are experiencing.
Instead of:

Code: Select all

{capture assign=maxresdefault}https://img.youtube.com/vi/{$item->fielddefs.video_id.value}/maxresdefault.jpg{/capture}
try using

Code: Select all

{$maxresdefault="https://img.youtube.com/vi/{$item->fielddefs.video_id.value}/maxresdefault.jpg"}
HTH
"There are 10 types of people in this world, those who understand binary... and those who don't."
* by the way: English is NOT my native language (sorry for any mistakes...).
Code of Condut | CMSMS Docs | Help Support CMSMS
My developer Page on the Forge
GeekMoot 2015 in Ghent, Belgium: I was there!
GeekMoot 2016 in Leicester, UK: I was there!
DevMoot 2023 in Cynwyd, Wales: I was there!
User avatar
DIGI3
Dev Team Member
Dev Team Member
Posts: 1609
Joined: Wed Feb 25, 2009 4:25 am
Location: Victoria, BC

Re: Get image dimensions with a plugin

Post by DIGI3 »

Not an answer to your question, but I think the youtube api provides a method for obtaining the thumbnail dimensions. Might be more efficient, if so.
Not getting the answer you need? CMSMS support options
User avatar
velden
Dev Team Member
Dev Team Member
Posts: 3483
Joined: Mon Nov 28, 2011 9:29 am
Location: The Netherlands

Re: Get image dimensions with a plugin

Post by velden »

Also not an answer to your question but can you imagine how inefficient it is to calculate the dimensions of an remote image on every visit of a page?
Especially considering the odds the values change over time.

Further, not sure if I'm right, it seems you're trying to get two remote images for every item. Then get the size of both, if the first fails use the second.
Again the efficiency won't be optimal.

If I were you, I'd consider looking at the LISE api, add some fields if necessary and use a cmsms event - on saving the the LISE item - to trigger your logic and save the results with the LISE item itself.
webform
Power Poster
Power Poster
Posts: 458
Joined: Sat Nov 25, 2006 3:39 pm
Location: Copenhagen, Denmark

Re: Get image dimensions with a plugin

Post by webform »

Thanks all for some very valuable tips and insights.

I've tried something different based on your suggestions:

I've created an UDT for testing if the YouTube URL are available:

Code: Select all

$url = isset($params['url']) ? $params['url'] : '';
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, true);   
curl_setopt($ch, CURLOPT_NOBODY, true);    
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.4");
curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch, CURLOPT_TIMEOUT,10);
curl_setopt($ch, CURLOPT_ENCODING, "gzip");
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
$output = curl_exec($ch);
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

echo $httpcode;
And my portfolio template i've removed "capture" and moved the fetching of the fallback image $hqdefault so it doesn't get fetched unless there is no $maxresdefault image.
I'm already experiencing a vast improvement in load time (although it probably could get better)

Code: Select all

{if $items|@count > 0}{strip}
<!-- Portfolio -->
<div id="portfolio" class="grid-layout portfolio-3-columns" data-margin="0">
{foreach from=$items item=item name=portfolio}
    <!-- portfolio item -->
    <div class="portfolio-item{if $smarty.foreach.portfolio.first || $smarty.foreach.portfolio.iteration is div by 8} large-width{/if} img-zoom">
        <div class="portfolio-item-wrap">
            <div class="portfolio-image">
                <a data-lightbox="iframe" href="https://www.youtube.com/watch?v={$item->fielddefs.video_id.value}">
	                {if $item->fielddefs.poster_image.value}
	                <img src="{CGSmartImage src1=$item->fielddefs.poster_image->GetImagePath(true) src2=$item->fielddefs.poster_image->value filter_croptofit='800,450,c' noauto=1}" alt="">
		            {else}
		            {$maxresdefault="https://img.youtube.com/vi/{$item->fielddefs.video_id.value}/maxresdefault.jpg"}
		            {$check="{check_404 url=$maxresdefault}"}
		            {if $check == '200'}
		            {CGSmartImage src=$maxresdefault filter_croptofit='800,450,c' noauto=1}
		            {else}
		            {$hqdefault="https://img.youtube.com/vi/{$item->fielddefs.video_id.value}/hqdefault.jpg"}
		            {CGSmartImage src=$hqdefault filter_croptofit='480,270,c' noauto=1}
		            {/if}
		            {/if}
		         </a>
            </div>
            <div class="portfolio-description">
                <a class="btn btn-light btn-rounded" title="{$item->title}" data-lightbox="iframe" href="//www.youtube.com/watch?v={$item->fielddefs.video_id.value}">{$item->title}</a>
            </div>
        </div>
    </div>
    <!-- end: portfolio item -->  
{/foreach}
</div>
<!-- end: Portfolio -->
{/strip}{/if}
User avatar
velden
Dev Team Member
Dev Team Member
Posts: 3483
Joined: Mon Nov 28, 2011 9:29 am
Location: The Netherlands

Re: Get image dimensions with a plugin

Post by velden »

I think if you do the checks for the two images on the saving of the LISE item you will be good.
Then you only need to check once which file to use.

CGSmartImage will do the caching of the cropped image for you.

If you still want to do the check from within the template, I just found this article about checking the existence of a file without actually downloading it.
https://thisinterestsme.com/check-see-h ... xists-php/

I can't see if you're already using this. But remember, it still has to do the request etc. So better would be to do it once, set and forget.
webform
Power Poster
Power Poster
Posts: 458
Joined: Sat Nov 25, 2006 3:39 pm
Location: Copenhagen, Denmark

Re: Get image dimensions with a plugin

Post by webform »

Thanks!

Yes, i'm doing a http header check, to see if the image exist and load the correct image accordingly.

You saying i could do this already at the creation of the LISE item?
User avatar
velden
Dev Team Member
Dev Team Member
Posts: 3483
Joined: Mon Nov 28, 2011 9:29 am
Location: The Netherlands

Re: Get image dimensions with a plugin

Post by velden »

I think it's possible yes.

LISE has a useful API and you can connect a UDT which does the checks to a LISE event (Extensions > Event Manager).

You will need some php knowledge and some knowledge of the LISE api.

But check the help of the LISE PreItemSave event

Code: Select all

//use for pre-save item event

$item = $params['item_object'];

...do your logic here---

$item->YourFieldAliasHere = 'YourValueHere';
Think that shoud do it.
webform
Power Poster
Power Poster
Posts: 458
Joined: Sat Nov 25, 2006 3:39 pm
Location: Copenhagen, Denmark

Re: Get image dimensions with a plugin

Post by webform »

Thanks! I'll look into it as it maybe also could help some more on the load speed of the page.
webform
Power Poster
Power Poster
Posts: 458
Joined: Sat Nov 25, 2006 3:39 pm
Location: Copenhagen, Denmark

Re: Get image dimensions with a plugin

Post by webform »

Thanks for all the suggestions, feedback and help.

I've got a working solution now with only a 1/3 of previous load time.

Here is what i did (if it's to any use of others):

My UDT (remote_youtube_poster_image):

Code: Select all

$item = $params['item_object'];

// Remote image URL
$url = 'https://img.youtube.com/vi/'.$item->video_id.'/maxresdefault.jpg';
// Remote fallback image URL
$fallbackurl = 'https://img.youtube.com/vi/'.$item->video_id.'/hqdefault.jpg';

// Save path to uploads folder
$path = $_SERVER['DOCUMENT_ROOT'].'/uploads/images/portfolio/img-'.$item->video_id.'.jpg';

// Image path to LISE field
$img = '/uploads/images/portfolio/img-'.$item->video_id.'.jpg';

//Check Availability
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, true);   
curl_setopt($ch, CURLOPT_NOBODY, true);    
curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.0.3) Gecko/2008092417 Firefox/3.0.4");
curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch, CURLOPT_TIMEOUT,10);
curl_setopt($ch, CURLOPT_ENCODING, "gzip");
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
$output = curl_exec($ch);
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);

if ($httpcode == '200') {

// Save maxresdefault image
$ch = curl_init($url);
$fp = fopen($path, 'wb');
curl_setopt($ch, CURLOPT_FILE, $fp);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_exec($ch);
curl_close($ch);
fclose($fp);

} else {

// Save hqdefault image
$ch = curl_init($fallbackurl);
$fp = fopen($path, 'wb');
curl_setopt($ch, CURLOPT_FILE, $fp);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_exec($ch);
curl_close($ch);
fclose($fp);

}

$item->poster_image_youtube = $img;
The UDT i connected to my LISE PreItemSave in Event Manager.

My LISE Summary Template:

Code: Select all

{if $items|@count > 0}{strip}
<!-- Portfolio -->
<div id="portfolio" class="grid-layout portfolio-3-columns" data-margin="0">
{foreach from=$items item=item name=portfolio}
    <!-- portfolio item -->
    <div class="portfolio-item{if $smarty.foreach.portfolio.first || $smarty.foreach.portfolio.iteration is div by 8} large-width{/if} img-zoom">
        <div class="portfolio-item-wrap">
            <div class="portfolio-image">
                <a data-lightbox="iframe" href="https://www.youtube.com/watch?v={$item->fielddefs.video_id.value}">
	                {if $item->fielddefs.poster_image.value}
	                <img src="{CGSmartImage src1=$item->fielddefs.poster_image->GetImagePath(true) src2=$item->fielddefs.poster_image->value filter_croptofit='800,450,c' noauto=1}" alt="">
		            {else}
		            {$posterimg = $item->fielddefs.poster_image_youtube.value}
		            {$imagesize = getimagesize("{root_url}{$posterimg}")}
		            {if $imagesize[0] > '720'}
		            {CGSmartImage src=$posterimg filter_croptofit='800,450,c' noauto=1}
		            {else}
		            {CGSmartImage src=$posterimg filter_croptofit='480,270,c' noauto=1}
		            {/if}
		            {/if}
		         </a>
            </div>
            <div class="portfolio-description">
                <a class="btn btn-light btn-rounded" title="{$item->title}" data-lightbox="iframe" href="//www.youtube.com/watch?v={$item->fielddefs.video_id.value}">{$item->title}</a>
            </div>
        </div>
    </div>
    <!-- end: portfolio item -->  
{/foreach}
</div>
<!-- end: Portfolio -->
{/strip}{/if}
Locked

Return to “Modules/Add-Ons”