Solved

php: How do I display binary data as an image (png) on a browser?

Posted on 2008-10-09
30
3,988 Views
Last Modified: 2013-12-20
I am building a website for a corporation that would like to display a stock chart on one of its pages.
I am accessing a URL: http://www.stockwatch.com/webservice/CorporateServices.asmx/CorporateChart (username & password needed)

It returns an XML file where the tag ChartImage contains a png image.

The closest I have come to displaying it is to show a series of binary data on the browser:

Question: Would your code "translate" the binary data to png?

Here is my code so far:

mainpage.php (I have included 3 versions of trying to embed the data; the third one -include- returns binary data-)
------------------
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body >
<div id="wrapper">
<div class="main">
    <div class="mainRight">
            <H1>SHARE QUOTES AND CHARTS</H1>
                   
     <object type="image/png" data="../stock/chart.php" width="100" height="100"></object>
       
     <img src="../stock/chart.php" width="420px" height="400px" border="1"/>  
      
      <?php include "../stock/chart.php" ?>
 
</div>

chart.php
------------
<?php //header('Content-type: image/png');
function value_in($element_name, $xml, $content_only = true) {
    if ($xml == false) {
        return false;
    }
    $found = preg_match('#<'.$element_name.'(?:\s+[^>]+)?>(.*?)'.
            '</'.$element_name.'>#s', $xml, $matches);
    if ($found != false) {
        if ($content_only) {
            return $matches[1];  //ignore the enclosing tags
        } else {
            return $matches[0];  //return the full pattern match
        }
    }
    // No match found: return false.
    return false;
}

$xml = file_get_contents('http://www.stockwatch.com/webservice/CorporateServices.asmx/CorporateChart?Id=xyz&Pw=123&pars=width=420%26ph=400%26ih=100%26npdivs=20%26bc=0xfefefe%26fc=0x007dd0%26tc=ox007dd0%26sf=a%26sfs=8');

$channel_ChartImage = value_in('ChartImage', $xml);

echo $channel_ChartImage;
?>


Thank you,
Ina
0
Comment
Question by:InaByDesign
  • 16
  • 14
30 Comments
 
LVL 39

Expert Comment

by:Roger Baklund
ID: 22682783
XML can not store binary data directly, it must be encoded. The most common encoding used for binary data in XML is base64. PHP has a standard function for decoding base64-encoded data:

http://se2.php.net/manual/en/function.base64-decode.php
0
 

Author Comment

by:InaByDesign
ID: 22682823
If I decode the binary data I still do not get a picture:

Data before encoding:
iVBORw0KGgoAAAANSUhEUgAAAaQAAAIfCAYA
...
6nfvmXtlf9pcPyAc+ xgc+RlF9bkKfm5APyAfkA1f7wP8H9aWOxVfjZ80AAAAASUVORK5CYII=

Data after encoding:
ÿýPNG  ýýý IHDRýýýýýýýý?ýýýýýsRGBýýýýýýýgAMAýýýý ýaýýý cHRMýýz&ýýýýýýýýýýýýýýu0ýýý`ýý:ýý
....
ýtý`ýýýý|ýCRýHýhýýýý@ýv&9ýfu)ý>;xýýýýýýmy÷ýýý>ýý{ý'ýtýLý{+ ýfuýýý^ý_ýý>ý>FQ}nBýýýýýWýýýýýWýgýýýýýIENDýB`ý

Modified code:

<?php //header('Content-type: image/png');
function value_in($element_name, $xml, $content_only = true) {
    if ($xml == false) {
        return false;
    }
    $found = preg_match('#<'.$element_name.'(?:\s+[^>]+)?>(.*?)'.
            '</'.$element_name.'>#s', $xml, $matches);
    if ($found != false) {
        if ($content_only) {
            return $matches[1];  //ignore the enclosing tags
        } else {
            return $matches[0];  //return the full pattern match
        }
    }
    // No match found: return false.
    return false;
}

$xml = file_get_contents('http://www.stockwatch.com/webservice/CorporateServices.asmx/CorporateChart?Id=AeroplanXML&Pw=1000842&pars=width=420%26ph=400%26ih=100%26npdivs=20%26bc=0xfefefe%26fc=0x007dd0%26tc=ox007dd0%26sf=a%26sfs=8');

$channel_ChartImage = value_in('ChartImage', $xml);

$image_64 = base64_decode($channel_ChartImage);
echo $image_64;
?>

0
 
LVL 39

Expert Comment

by:Roger Baklund
ID: 22682943
The 'PNG' at the start of the decoded data indicates that the conversion was correct.

You need to un-comment the first line:

header('Content-type: image/png');
0
 

Author Comment

by:InaByDesign
ID: 22683002
I still do not get an image:

The output on the browser is the following:

from <img src="../images/layout/clear.gif" width="100%" height="18px" /> ==> nothing
from <object type="image/png" data="../stock/chart.php" width="100" height="100"></object> ==> nothing
from <img src="../stock/chart.php" width="420px" height="400px" border="1"/> ==> empty box in FF
from <?php include "../stock/chart.php" ?> ==> the following in FF: ÿ
Warning: Cannot modify header information - headers already sent by (output started at E:\XAMPP\htdocs\aeroplan\ga\pages\invQuote.php:8) in E:\XAMPP\htdocs\aeroplan\ga\stock\chart.php on line 1
ýPNG  ýýý IHDRýýýýýýýý?ýýýýýsRGBýýýýýýýgAMAýýýý ýaýýý
the following in IE7: Warning: Cannot modify header information - headers already sent by (output started at E:\XAMPP\htdocs\aeroplan\ga\pages\invQuote.php:8) in E:\XAMPP\htdocs\aeroplan\ga\stock\chart.php on line 1
ýPNG  IHDRý?ýýsRGBýýýgAMAýý ýa cHRMz&ýýýýýu0ý`:ýpýýQ<`ýIDATx^ýýaý$)ýlý-ýýaýý|ýý"ýýýýýýý
0
 
LVL 39

Expert Comment

by:Roger Baklund
ID: 22683147
Use only <img src="../stock/chart.php" width="420px" height="400px" border="1"/>  in your html, remove the <object> element and the PHP include. The latter is probably the cause of the warning.

There seems to be a space in the base64 data provided above, before the decoding, it should not be there...

Try using the below function to check if the bas64 encoded data is valid. Insert the comment on the header() function again, and run/include chart.php to see the output.
function is_base64_encoded($data) {

  return preg_match('%^[a-zA-Z0-9/+]*={0,2}$%', $data)); 

}
 

$channel_ChartImage = value_in('ChartImage', $xml);
 

echo is_base64_encoded($channel_ChartImage) ?

  'This is a valid base64 string':

  'This is NOT valid base64';

Open in new window

0
 
LVL 39

Expert Comment

by:Roger Baklund
ID: 22683186
Btw, the correct <img> should be like this, no "px" and no border attribute, but you need an alt attribute (mandatory according to html spec):

<img src="../stock/chart.php" width="420" height="400" alt="Bad image data" />

Open in new window

0
 

Author Comment

by:InaByDesign
ID: 22683237
When I ran the function preg_match I get "This is a valid base64 string" ==> so that is good

No image yet:

mainpage.php
------------------
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body >
<div id="wrapper">
<div class="main">

    <div class="mainRight">
            <H1>SHARE QUOTES AND CHARTS</H1>

     <img src="../stock/chart.php" width="420px" height="400px" border="1"/>  


</div>
    <!--class main -->

</div><!--class wrapper -->
</html>


<?php header('Content-type: image/png');

function is_base64_encoded($data) {
  return preg_match('%^[a-zA-Z0-9/+]*={0,2}$%', $data);
}

function value_in($element_name, $xml, $content_only = true) {
    if ($xml == false) {
        return false;
    }
    $found = preg_match('#<'.$element_name.'(?:\s+[^>]+)?>(.*?)'.
            '</'.$element_name.'>#s', $xml, $matches);
    if ($found != false) {
        if ($content_only) {
            return $matches[1];  //ignore the enclosing tags
        } else {
            return $matches[0];  //return the full pattern match
        }
    }
    // No match found: return false.
    return false;
}

 
$channel_ChartImage = value_in('ChartImage', $xml);
 
//echo is_base64_encoded($channel_ChartImage) ?
//  'This is a valid base64 string':
//  'This is NOT valid base64';
 
  $xml = file_get_contents('http://www.stockwatch.com/webservice/CorporateServices.asmx/CorporateChart?Id=ABC&Pw=123&pars=width=420%26ph=400%26ih=100%26npdivs=20%26bc=0xfefefe%26fc=0x007dd0%26tc=ox007dd0%26sf=a%26sfs=8');

$channel_ChartImage = value_in('ChartImage', $xml);

$image_64 = base64_decode($channel_ChartImage);
echo $image_64;
?>
0
 
LVL 39

Expert Comment

by:Roger Baklund
ID: 22683395
Change your <img> tag, include an alt attribute. Is the value of the alt attribute output where the image should have been?

There is a missing </div> in your html, but that is not the reason for your problem.

Make sure there is no newline after the ?> in chart.php.

Try adding this before the echo statement:
header('Content-Length: '.strlen($image_64;));

Open in new window

0
 
LVL 39

Expert Comment

by:Roger Baklund
ID: 22683400
Sorry, one ; too much:
header('Content-Length: '.strlen($image_64));

Open in new window

0
 
LVL 39

Expert Comment

by:Roger Baklund
ID: 22683449
A blank line before <?php will break the image, make sure <?php is on the very first line of chart.php.
0
 

Author Comment

by:InaByDesign
ID: 22683475
No, still nothing, but it displays 'chart data' thanks to the alt tag

Change your <img> tag, include an alt attribute. Is the value of the alt attribute output where the image should have been?
==> <img src="../stock/chart.php" width="420" height="400" alt="chart data" />

There is a missing </div> in your html, but that is not the reason for your problem.
==> thanks fixed it

Make sure there is no newline after the ?> in chart.php.
==> no newline; ?> is the last item

Try adding this before the echo statement:
==> done:

<?php header('Content-type: image/png');

function is_base64_encoded($data) {
  return preg_match('%^[a-zA-Z0-9/+]*={0,2}$%', $data);
}

function value_in($element_name, $xml, $content_only = true) {
    if ($xml == false) {
        return false;
    }
    $found = preg_match('#<'.$element_name.'(?:\s+[^>]+)?>(.*?)'.
            '</'.$element_name.'>#s', $xml, $matches);
    if ($found != false) {
        if ($content_only) {
            return $matches[1];  //ignore the enclosing tags
        } else {
            return $matches[0];  //return the full pattern match
        }
    }
    // No match found: return false.
    return false;
}

 
$channel_ChartImage = value_in('ChartImage', $xml);
 
  $xml = file_get_contents('http://www.stockwatch.com/webservice/CorporateServices.asmx/CorporateChart?Id=abc&Pw=123&pars=width=420%26ph=400%26ih=100%26npdivs=20%26bc=0xfefefe%26fc=0x007dd0%26tc=ox007dd0%26sf=a%26sfs=8');

$channel_ChartImage = value_in('ChartImage', $xml);

$image_64 = base64_decode($channel_ChartImage);
header('Content-Length: '.strlen($image_64));
echo $image_64;
?>
0
 
LVL 39

Expert Comment

by:Roger Baklund
ID: 22683562
hm... try running chart.php directly in the browser. Still no image output?

Try writing the data to a file, and check if the file is a valid png file, i.e. if it is viewable.
$image_64 = base64_decode($channel_ChartImage);

$f = fopen('test_chart.png','w');

fwrite($f,$image_64);

fclose($f);

Open in new window

0
 

Author Comment

by:InaByDesign
ID: 22683591
It DID create the file test_chart.png successfully.
It opens fine, is viewable and is exactly as should be.
0
 
LVL 39

Expert Comment

by:Roger Baklund
ID: 22683628
ok, thats good.

Check the image src attribute, is the path "../stock/chart.php" correct? If chart.php is in the same directory as the html page, it should be only "chart.php".
0
 

Author Comment

by:InaByDesign
ID: 22683645
It is not in the same directory, the path is correct.
0
Enabling OSINT in Activity Based Intelligence

Activity based intelligence (ABI) requires access to all available sources of data. Recorded Future allows analysts to observe structured data on the open, deep, and dark web.

 
LVL 39

Expert Comment

by:Roger Baklund
ID: 22683709
Can you post your current chart.php as an attachment? I'll replace file_get_contents() with some local png file I have, and check if it will run for me.
0
 

Author Comment

by:InaByDesign
ID: 22683744
Here you go!
chart.php.txt
0
 
LVL 39

Expert Comment

by:Roger Baklund
ID: 22683767
Why is the Content-Length header at the start? That won't work, the $image_64 is not defined yet. This header must come after this line:

$image_64 = base64_decode($channel_ChartImage);

Also, you must remove the file writing and include the echo... try this:
<?php 

header('Content-type: image/png');
 

function value_in($element_name, $xml, $content_only = true) {

    if ($xml == false) {

        return false;

    }

    $found = preg_match('#<'.$element_name.'(?:\s+[^>]+)?>(.*?)'.

            '</'.$element_name.'>#s', $xml, $matches);

    if ($found != false) {

        if ($content_only) {

            return $matches[1];  //ignore the enclosing tags

        } else {

            return $matches[0];  //return the full pattern match

        }

    }

    // No match found: return false.

    return false;

}

 

$xml = file_get_contents('url goes here');

$channel_ChartImage = value_in('ChartImage', $xml);

$image_64 = base64_decode($channel_ChartImage);

header('Content-Length: '.strlen($image_64));

echo $image_64; 
 

?>

Open in new window

0
 

Author Comment

by:InaByDesign
ID: 22683789
no... it did not work

0
 
LVL 39

Expert Comment

by:Roger Baklund
ID: 22683804
Did you remember to replace 'url goes here' with the actual url?
0
 

Author Comment

by:InaByDesign
ID: 22683828
yes, and I am attaching the 'calling' php
main.php.txt
0
 
LVL 39

Expert Comment

by:Roger Baklund
ID: 22683860
I think this part is ok. When you run chart.php directly in the browser, you should get the image.

I think it is very strange, writing $image_64 to a file results in a valid png file, but echoing it does not...

If you remove the content-type header again and call chart.php directly in the browser, do you se "rubbish" with PNG at the start? Or just a blank screen?
0
 

Author Comment

by:InaByDesign
ID: 22683939
No I do not get the image.

I removed both headers, otherwise I get warnings.


0
 
LVL 39

Expert Comment

by:Roger Baklund
ID: 22683959
Without the headers, it shouldn't output an image.

If you remove the headers and call chart.php DIRECTLY in the browser, do you se "rubbish" with PNG at the start? Or just a blank screen?
0
 

Author Comment

by:InaByDesign
ID: 22683969
"rubbish" with PNG at the start:

ýPNG  ýýý IHDRýýýýýýýý?ýýýýýsRGBýýýýýýýgAMAýýýý ýaýýý cHRMýýz&ýýýýýýýýýýýýýýu0ýýý`ýý:ýýýpýýQ<ýý`ýIDATx^ýaý$)ýlý-ýýaýý|ýý"ýýýýýýýsýLW8ýýýpýýýýýýýýýý, ýýýýýý'ýýýýýý ýýýýýýOýý|@> ýýýH@ýýv>SýýýýLýý\lýý»ýVýu6ýý^}ý2cýýýýýýýYý
0
 
LVL 39

Expert Comment

by:Roger Baklund
ID: 22684023
ok, thats good. That means we probably have the correct data, we just have problems outputting it correctly.

I'm running out of ideas here, so I am thinking about a work-around.

What if you create a file, and the <img> references the png file directly? You could check the age of the file, and re-create it every x minutes, depending on how often the data returned from the web service changes. You could include the company id in the file name. This would also ease the burden on the server. Is this a feasable approach?
0
 

Author Comment

by:InaByDesign
ID: 22686398
If we can successfully create a file and then read it can we not somehow bypass that step and show the result on the screen?

The following code works, but I how does writing to a file withstand heavy traffic?:
0
 
LVL 39

Accepted Solution

by:
Roger Baklund earned 500 total points
ID: 22686878
You probably don't need to write to the disk every time, when the same chart is displayed within a short time frame (how short?) you can skip the writing and just use the file created by a previous request.

Writing to a file every now and then is probably (depends on hardware) less work for the server than the combination of accessing the web service,  parsing the xml and base64-decoding it EVERY time.

So, how often would you need to re-create the file? Every hour? Every 10 minutes? See code below.

Is there only one chart per company? If there are more, the file name must include something more than the company id, something to make it unique for each chart.
<?php //header('Content-type: image/png');

 

function value_in($element_name, $xml, $content_only = true) {

    if ($xml == false) {

        return false;

    }

    $found = preg_match('#<'.$element_name.'(?:\s+[^>]+)?>(.*?)'.

            '</'.$element_name.'>#s', $xml, $matches);

    if ($found != false) {

        if ($content_only) {

            return $matches[1];  //ignore the enclosing tags

        } else {

            return $matches[0];  //return the full pattern match

        }

    }

    // No match found: return false.

    return false;

}

 

$company_id = 'abc'; 
 

$filename = 'chart_'.$company_id.'.png';

$cache_time = 10 * 60; # 10 minutes
 

$refresh = true;

if(file_exists($filename)) { # check if re-create is needed

  $stat = stat($filename);

  if($stat['mtime'] > time() - $cache_time) $refresh = false;

} 

if($refresh) { 

  $xml = file_get_contents('http://www.stockwatch.com/webservice/CorporateServices.asmx/CorporateChart?'.

    'Id='.$company_id.

    '&Pw=123&pars=width=420%26ph=400%26ih=100%26npdivs=20%26bc=0xfefefe%26fc=0x007dd0%26tc=ox007dd0%26sf=a%26sfs=8');

  $channel_ChartImage = value_in('ChartImage', $xml);

  $image_64 = base64_decode($channel_ChartImage);

  $f = fopen($filename,'w');

  fwrite($f,$image_64);

  fclose($f);

}
 

echo '<img src="'.$filename.'" alt="chart data" />';

?>

Open in new window

0
 

Author Comment

by:InaByDesign
ID: 22687456
I just found out that the chart needs to be updated only once a day!

There is only one company and only one chart.

So your solution (which started as a workaround) is better than what I wanted to implement in the first place.

Writing to a file once a day has to be less work for the server than the combination of accessing the web service,  parsing the xml and base64-decoding it EVERY time.

Thank you and you deserve your points!
0
 

Author Closing Comment

by:InaByDesign
ID: 31504844
Thank you very much!
This was a rush job and I really appreciate your help.
0

Featured Post

Threat Intelligence Starter Resources

Integrating threat intelligence can be challenging, and not all companies are ready. These resources can help you build awareness and prepare for defense.

Join & Write a Comment

Nothing in an HTTP request can be trusted, including HTTP headers and form data.  A form token is a tool that can be used to guard against request forgeries (CSRF).  This article shows an improved approach to form tokens, making it more difficult to…
This article discusses four methods for overlaying images in a container on a web page
Any person in technology especially those working for big companies should at least know about the basics of web accessibility. Believe it or not there are even laws in place that require businesses to provide such means for the disabled and aging p…
The viewer will get a basic understanding of what section 508 compliance can entail, learn about skip navigation links, alt text, transcripts, and font size controls.

744 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

10 Experts available now in Live!

Get 1:1 Help Now