2

When I try to print an image to STDOUT in a Perl CGI script, the image gets clipped when viewed in the browser.

Here is the following code:

if ($path =~ m/\.jpe?g$/i)
{    
  my $length = (stat($path))[7];
  $| = 1;
  print "Content-type: image/jpg\r\n";
  print "Content-length: $length\r\n\r\n";
  open(IMAGE,"<$path");
  binmode(IMAGE);
  binmode(STDOUT);
  my ($image, $buff);
  read IMAGE, $buff, $length;
  syswrite STDOUT, $buff, $length;
  close IMAGE;
}
mercator
  • 28,290
  • 8
  • 63
  • 72
Jeremy Gwa
  • 2,333
  • 7
  • 25
  • 31

3 Answers3

5

If you really want to read the entire file into memory before serving, use File::Slurp:

#!/usr/bin/perl

use strict; use warnings;

use CGI::Simple;
use File::Slurp;
use File::stat;

local $| = 1;

my $cgi = CGI::Simple->new;

my $st = stat($path) or die "Cannot stat '$path'";

print $cgi->header(
    -type => 'image/jpeg',
    -length => $st->size,
);

write_file(\*STDOUT, {binmode => ':raw'}, 
    \ read_file( $path, binmode => ':raw' )
);

However, reading the entire file will consume large amounts of memory for large images. Therefore, see How can I serve an image with a Perl CGI script?.

Community
  • 1
  • 1
Sinan Ünür
  • 116,958
  • 15
  • 196
  • 339
  • if he calls `read` with the `$length` of the image, isn't it read in just one call? – ax. Feb 05 '10 at 15:24
  • i read the doc. and i do see a need for returning the number of characters actually read: when reading at the end of the file. example: a file of 240 byte, with `$length` 100: first read returns 100, second read 100, third/last read 40. however, when `$length` is the size of the file, `read` should read the whole file or return an error, but not something in between. shouldn't it? – ax. Feb 05 '10 at 15:47
  • @ax You are correct. Thank you. Cleaning up my post and comments. The OP still needs to check the return value of `read` and `syswrite`, though. – Sinan Ünür Feb 05 '10 at 16:19
2

EDIT: as the stat doesn't seem to be problem, some more ideas:

try using unbuffered instead of buffered reading, ie. use sysread instead of read. or the other way round: use both buffered read and write. also, try commenting out the $|. see Suffering from Buffering? for details on perl buffered io. see also How can I serve an image with a Perl CGI script? here on SO for an apparently working solution. EDIT END

you are using the wrong stat field. (stat($path))[10] is ctime: inode change time in seconds since the epoch. it should be (stat($path))[7], size: total size of file, in bytes.

Community
  • 1
  • 1
ax.
  • 58,560
  • 8
  • 81
  • 72
  • It does not matter if I include content-length or not. I changed the stat as you suggested, but still the image gets cut off – Jeremy Gwa Feb 05 '10 at 12:38
  • i just tried your (updated) code here: it is working. so i guess it is something with your image. mind to share a link? – ax. Feb 05 '10 at 13:09
  • could it be a server/installation issue? – Jeremy Gwa Feb 05 '10 at 13:24
  • maybe. but i think it's more probable that it has to do with perl's buffered io. see my **EDIT** above. – ax. Feb 05 '10 at 14:24
0

FYI: I have come to the conclusion that the images are in fact corrupt, though they are fully viewable in Windows File Explorer.

The FireFox browser shows the Images clipped(no matter how they are accessed, so I guess this is no longer a Perl problem), but the Safari Browser displays them completely.

The images were re sampled from using Java's imageIO in "jpg" mode. I just changed the mode to "png", and now the newly generated images are showing perfectly in all browsers. So this was actually a Java imageIO issue.

It is solved.

Thank you everyone for your responses.

Jeremy Gwa
  • 2,333
  • 7
  • 25
  • 31