Link to home
Start Free TrialLog in
Avatar of InaByDesign
InaByDesign

asked on

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

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
Avatar of Roger Baklund
Roger Baklund
Flag of Norway image

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
Avatar of InaByDesign
InaByDesign

ASKER

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;
?>

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');
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ýý|ýý"ýýýýýýý
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

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

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;
?>
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

Sorry, one ; too much:
header('Content-Length: '.strlen($image_64));

Open in new window

A blank line before <?php will break the image, make sure <?php is on the very first line of chart.php.
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;
?>
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

It DID create the file test_chart.png successfully.
It opens fine, is viewable and is exactly as should be.
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".
It is not in the same directory, the path is correct.
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.
Here you go!
chart.php.txt
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

no... it did not work

Did you remember to replace 'url goes here' with the actual url?
yes, and I am attaching the 'calling' php
main.php.txt
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?
No I do not get the image.

I removed both headers, otherwise I get warnings.


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?
"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ý
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?
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?:
ASKER CERTIFIED SOLUTION
Avatar of Roger Baklund
Roger Baklund
Flag of Norway image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
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!
Thank you very much!
This was a rush job and I really appreciate your help.