bret.foreman has asked for the wisdom of the Perl Monks concerning the following question:

After getting excellent monk advice in another thread concerning the best options for scientific graphing (thanks again for that), I settled on using the external package gnuplot. For patent reasons, it generates images in PNG format instead of GIF, which I think should work fine with all the more recent browsers.

The start of my cgi puts up an h2 header and a scrolling menu where the user selects a dataset file, then clicks a "show me" button and then I want a graph to appear, either in the same window below the menu or in a new window - I don't care which.

I use open3 to launch gnuplot in a mode where it accepts commands and data in stdin, sends errors to stderr, and sends the image to stdout. (Please no warnings about deadlocks with open3, I know all about that.) So my PNG image ends up in a file handle returned by open3, which I'll call $png_image_fh. This all works fine and an examination of the PNG data looks apparantly correct, though we won't know until I can actually display it as an image.

Now, I need to read the data from the $png_image_fh and print it out as a PNG image. Looking around in the perlmonk faq, I found this thread: http://www.perlmonks.com/index.pl?node_id=18565 from which I generated the following code:

my ($image_buf,$chunk); while ( read( $png_image_fh , $chunk , 1024 ) ) { $image_buf .= $chunk; } print "Content-type: image/png\n\n"; # binmode included in case we run on Windoze binmode STDOUT; print $image_buf;
Instead of an image, what I get is the PNG data printed in text mode. Any ideas why this might be? The server is Linux, the browser is IE 6.

Thanks
Bret

Replies are listed 'Best First'.
Re: Getting the picture
by gaal (Parson) on Aug 11, 2004 at 16:45 UTC
    First the arrangement, and apologies if you already know this. The image should be sent over the wire in response to a separate HTTP request. Your generated HTML should just include an IMG tag (or have Javascript that writes an IMG tag on demand); the browser, seeing this, will make the request for the image.

    Now, the above means that you can basically point your browser to the URL given in the first page, and expect to receive an image/png response containing your image. So do just that: request the image manually. Look at the HTTP headers. One of them should contain the Content-type, before the double newline separator. If you have LWP installed, you can use GET -U http://... on the command line to examine the headers. Post the header here if you spot no problems.

      Forgive me for being a little slow about all this. From your comments above, I presume I should "print" the IMG tag as explicit html code to the browswer, but what should be the exact string for that tag in this context? Remember that, even though there is an image file handle, there is no image file - it exists only in memory. Can the IMG tag specify that?

      Bret

        You need to provide a URL that somehow encodes everything you need to retrieve the image with, and — yes — have a separate CGI/handler/whatever provide the actual image data.

        You may defer creating the data to the second request, if you like: that means no temporary files, and that the IMG url needs to contain ebough info to *create* the image. Alternatively, you can have the first request create the image, store it somewhere (DB or file, doesn't really matter) and only communicate a request ID to the client, which is used to fetch the precomputed image.

        You have to think a bit about security here, considering that the second request may need its own authentication so that other people can't "steal" somebody else's image. Also, there's a nice feature of using stored and precomputed images in that caching becomes very easy: *anyone* who requests an image with the paramaters that have already been computed (and optionally, who also passes authentication) will get the data very quickly. You can have a scheduled task that clears up old data from sotrage to prevent it from growing too large (or too stale).

        One common way to do this is to save the image to a temp directory, then print out an <IMG SRC="..."> line that points to that temp file. Another process can periodically delete old images.

        If you're feeling lucky, you could try using a data URL to embed the image directly into the HTML, but it's not supported in all browsers, and will only work for small files IIRC.

Re: Getting the picture
by davorg (Chancellor) on Aug 11, 2004 at 16:37 UTC

    Have you put $png_image_fh in binmode as well?

    Have you printed another content-type header before this?

    --
    <http://www.dave.org.uk>

    "The first rule of Perl club is you do not talk about Perl club."
    -- Chip Salzenberg

      No, I wasn't putting $png_image_fh in binmode, but I just tried it with no change in behavior.

      No, I am not printing a content-type header before this.

      Bret

Re: Getting the picture
by CountZero (Bishop) on Aug 11, 2004 at 18:49 UTC
    An easy way to dynamically generate graphs directly from an Apache handler, just by linking the <img>-tag to a virtual path on the webserver is provided by Apache::GD::Graph.

    Works like a charm!

    CountZero

    "If you have four groups working on a compiler, you'll get a 4-pass compiler." - Conway's Law

Re: Getting the picture
by ikegami (Patriarch) on Aug 11, 2004 at 17:42 UTC

    Can you display other .png files? (There's one here.) Keep in mind nothing at all can be printed to stdout before the Content-type line.

    lynx -mime_header -source -dump http://www.server.com/graph.cgi | less should look like:

    HTTP/1.1 200 OK Date: Wed, 11 Aug 2004 17:25:39 GMT Server: Apache/1.3.31 (Unix) mod_fastcgi/2.4.2 Last-Modified: Sat, 05 Jun 2004 08:20:37 GMT ETag: "2501b-13d6-40c18255" Accept-Ranges: bytes Content-Length: 5078 Connection: close Content-Type: image/png <89>PNG...gibberish...

    Important items to note are 1) the complete lack of HTML anywhere in the result, 2) "Content-Type: image/png", 3) no blank lines at any point before "Content-Type: image/png", and 4) the text "<89>PNG" or similar immediately following the blank line followed by lots of gibberish. If your output is deviating from this, what do you see?

Re: Getting the picture
by eserte (Deacon) on Aug 11, 2004 at 18:20 UTC
    For patent reasons, it generates images in PNG format instead of GIF
    The patent expired a few weeks ago. For example, libgd and GD.pm now come again with GIF support.
Re: Getting the picture
by jaco (Pilgrim) on Aug 11, 2004 at 16:54 UTC
    are you on windows?
    $png = "test.png"; open(PNG, "$png"); print "Content-type: image/png\n\n"; print do { local $/; <PNG> };
    but like gaal said, you have to call it as the image src.
      My browser is IE 6 on Win2K. The server is Apache on Linux.

      Bret