Tuesday, June 26, 2007

Streaming images from Web Applications

  

Streaming images from Web Applications

By: Corbin Dunn

Abstract: How to stream jpeg's, gif's, files, resource's, creating images on the fly, and setting MIME type information.

Advanced Web Applications

Streaming Images from Web Applications
By Corbin Dunn

You may have had an instance when you wanted to stream a custom image out to a web page based on what the user selected. This document describes several different ways to store and send images to a web browser.

Download this whole project.

Sending a jpeg image from the hard drive
This tutorial was created with Delphi 5, but it should work fine with previous versions. You will need the Enterprise (or Client/Server) version of Delphi to create Web Applications.


Delphi Example

Create a new Web Application (File->New->Web Server Application, ISAPI DLL) and add a new Action. Set the PathInfo to '/motorcycle.jpg' and in the OnAction event add the following code:

procedure TWebModMain.WebModMainwbactnGetJpegAction(Sender: TObject;
Request
: TWebRequest; Response: TWebResponse; var Handled: Boolean);

// Helper function to get the current directory the DLL is in
function GetDllDir
: string;
begin
SetLength
(Result, MAX_PATH+1); // Add 1 for the null character
GetModuleFileName
(hInstance, PChar(Result), MAX_PATH+1);
SetLength
(Result, Length(PChar(Result)));
Result
:= ExtractFilePath(Result);
end
;
var
FileStream
: TFileStream;
begin
// This demonstrates returning a Jpeg file from a file on the hard drive.
try
FileStream
:= TFileStream.Create(GetDllDir + 'motorcycle.jpg', fmOpenRead);
// Note that the file 'motorycle.jpg' must be in the same
// directory as this DLL.
FileStream
.Position := 0; // Go to the start of the stream
Response
.ContentStream := FileStream;
Response
.ContentType := 'image/jpeg';
Response
.SendResponse;
// Notice that the stream is not freed (it shouldn't be!)
except
// Catch all exceptions and show a custom message
on E
: Exception do
Response
.Content := '<html><body>An error occurred ' +
E
.ClassName + ': ' + E.Message + '</body></html>';
end
;
end
;





The first thing you will see is the helper function GetDllDir that simply get's the current directory the DLL resides in. You will have to put the file 'motorcycle.jpg' in the same directory as your ISAPI dll for this to work correctly.

Something to notice is how the whole function is wrapped in a try..except so that I can show a custom error message if something goes wrong. The TFileStream.Create can potentially raise an exception if the file passed to it doesn't exist or is locked.


Next, the Response.ContentStream is set to the FileStream we just created. Once you do this note that you should not free the stream. It is taken care of for you in the TWebResponse destructor.


Then the Response.ContentType is set based on what we are sending (a Jpeg in this case), and the Response is sent with Response.SendResponse.


One thing which you may want to do is send other file types. A problem you may encounter is what to set the Response.ContentType to. This can easily be solved in code by looking up the ContentType in the registry:

// MimeType is a string
Reg
:= TRegistry.Create;
try
Reg
.RootKey := HKEY_CLASSES_ROOT;
if Reg
.OpenKey(ExtractFileExt(FileName), False) then
begin
if Reg
.ValueExists('Content Type') then
MimeType
:= Reg.ReadString('Content Type');
Reg
.CloseKey;
end
;
finally
Reg
.Free;
end
;

Of course, if the ContentType wasn't found, you will probably want to default to something such as 'text/plain' (plain text).

Another thing which you may be interested in doing is setting the "suggested file name" when a user goes to download a file by sending back a response other than an image. This can easily be done by adding a custom Content-Disposition header field:


Response.CustomHeaders.Add('Content-Disposition=; ' +
'filename="Suggested Name.ext"');


If you would like to know more about what you can do with the Content-Disposition header, take a look at the RFC: http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html








Sending an image from a resource in the DLL
At one point I wanted to create an ISAPI dll that was completely self contained with its own images. All I had to output was a simple checked or unchecked image, so I decided a simple way to solve this problem was to put the GIF images in the resource portion of the DLL.

The first thing I did was to create a text file named "web_gifs.rc". Inside it, I had the lines:

  CHECKED GIF "checked.gif"
UNCHECKED GIF "unchecked.gif"
The two gif files were in the same directory as the "web_gifs.rc" file, and I compiled it into a resource file (.res) with a command line of:
brcc32 web_gifs.rc
For ease of use, I included a simple batch file that does this for you. I then added two new Actions to my project: one with a PathInfo of '/unchecked.gif' and another with a PathInfo of '/checked.gif'. I then created on OnAction event, and hooked them both to the same event with the following code in the event:



// Link the Gif's into the DLL resource section
{$R web_gifs.RES}
procedure TWebModMain
.WebModMainwbactnGetGifAction(Sender: TObject;
Request
: TWebRequest; Response: TWebResponse; var Handled: Boolean);
var
ImageType
: string;
begin
try
// Two Web Actions are hooked up to this single OnAction.
// Based on which PathInfo is in the Action, we send the
// correct Gif image.
if
(Sender as TWebActionItem).PathInfo = '/unchecked.gif' then
ImageType
:= 'UNCHECKED' // Resource name in .RES file
else
ImageType
:= 'CHECKED';
Response
.ContentStream := TResourceStream.Create(hInstance, ImageType, 'GIF');
Response
.ContentType := 'image/gif';
Response
.SendResponse;
except
on E
: Exception do
Response
.Content := '<html><body>An error occurred ' +
E
.ClassName + ': ' + E.Message + '</body></html>';
end
;
end
;




Based on the previous example, this one should be very easy to understand.







Dynamically creating a custom image
The next challenge was to dynamically create a custom image at run-time and return it to the user. Conceptually, the way this will be done is to create a TBitmap, draw on that Bitmap's Canvas, and then save that Bitmap to a TJpegImage. The JpegImage can then be streamed out to the web browser as before.





Delphi Example

I created another WebAction and set the PathInfo to '/custom.jpg'. The OnAction event is as follows:
procedure TWebModMain.WebModMainwbactnGetOnFlyAction(Sender: TObject;
Request
: TWebRequest; Response: TWebResponse; var Handled: Boolean);
var
Bitmap
: TBitmap; // The unit Graphics must be in your uses clause
JpegImage
: TJpegImage; // The unit JPEG must be in your uses clause
MemoryStream
: TMemoryStream;
I
: Integer;
begin
// This demonstrates creating a custom image on the fly,
// converting it from a Bitmap to a Jpeg, and sending the
// Jpeg to the browser.
try
Bitmap
:= TBitmap.Create;
try
// Draw some custom stuff on the bitmap
Bitmap
.Width := 300;
Bitmap
.Height := 300;
for I
:= 0 to Bitmap.Height - 1 do
begin
// Add a little color
Bitmap
.Canvas.Pen.Color := Trunc(I/Bitmap.Height*255);
Bitmap
.Canvas.MoveTo(0, I);
Bitmap
.Canvas.LineTo(Bitmap.Width -1, I);
end
;
// Set the background mode to be transparent when writing text.
SetBkMode
(Bitmap.Canvas.Handle, TRANSPARENT);

// Write a custom message based on some of the Request properties
Bitmap
.Canvas.Font.Color := clWhite;
Bitmap
.Canvas.TextOut(5, 5, 'Hello Host ' + Request.RemoteHost +
' (' + Request.RemoteAddr + ')');
Bitmap
.Canvas.TextOut(5, 30, 'You are using ' + Request.UserAgent);
Bitmap
.Canvas.TextOut(5, 55, 'The date/time is ' + DateTimeToStr(Now));

JpegImage
:= TJPEGImage.Create;
try
// Assign the Bitmap to the Jpeg
JpegImage
.Assign(Bitmap);
MemoryStream
:= TMemoryStream.Create;
JpegImage
.SaveToStream(MemoryStream);
MemoryStream
.Position := 0;
Response
.ContentStream := MemoryStream;
Response
.ContentType := 'image/jpeg';
Response
.SendResponse;
finally
JpegImage
.Free;
end
;
finally
Bitmap
.Free;
end
;
except
on E
: Exception do
Response
.Content := '<html><body>An error occurred ' +
E
.ClassName + ': ' + E.Message + '</body></html>';
end
;
end
;




A new thing to notice in this example is the saving the Jpeg to a TMemoryStream. The Jpeg and Bitmap can safely be freed, but the MemoryStream is automatically freed for you in the TWebResponse's destructor.








Putting it all together

When you want to display any of the images, all you have to do is type in the path to the DLL and add the right PathInfo. More commonly, you will probably embed it into an <IMG> tag, such as: <img src="/scripts/ImageStreamer.dll/custom.jpg">


The last thing to do in this example is to put it all together. I created one more action and set the Default property to True. In the OnAction event I added the following code:

procedure TWebModMain.WebModMainwbactnDefaultAction(Sender: TObject;
Request
: TWebRequest; Response: TWebResponse; var Handled: Boolean);
begin
// Default action, display several different images
// that were streamed out by the ISAPI dll
Response
.Content := '<html><body>' + #10#13 +
'<h2>Below are some images sent out from this Web Application</h2>' +
'<b>Here is a Gif that was streamed out from a resource inside ' +
'the Dll: checked <img src="' +
Request
.URL + '/checked.gif"> and unchecked: '+
'<img src="' + Request.URL + '/unchecked.gif"><br><br>' +
'Here is a Jpeg that was loaded from a file:<br>' +
'<img src="' + Request.URL + '/motorcycle.jpg"><br><br>' +
'Here is a custom image created on the fly:<br>' +
'<img src="' + Request.URL + '/custom.jpg">' +
'</b></body></html>';
end
;


Published on: 12/17/1999 12:00:00 AM


Server Response from: USSVS-BDN9


Borland® Copyright© 1994 - 2007 Borland Software Corporation. All rights reserved.

0 Comments: