Skip navigation.

Jeffrey Richter Did Come To TownAll recent postsSee Image Replacement In Action (Beta 3)!

.IR: Image Replacement With ASP.NET (Beta 2)

I've made some progress with the image replacement solution for ASP.NET since the first beta. Morgan Skinner, whose name should be well known to MSDN Magazine subscribers, pointed me to his article on quantization. With his code doing post-processing of dynamically generated images I can proudly say this is looking much much better! Hats off to Morgan!

I've made some changes to the code now in beta 2 (shall we give it a goofy name along the lines of "Whidbey"?)

  • The source code includes Morgan's Quantizer and OctreeQuantizer classes. If you're curious how Octree-based Quantization works, see his article.
  • The image handler produces GIFs, not JPEGs. Empirically I figured out the combination of GIF + quantization renders much cleaner images.
  • There was a bug with StringFormat.GenericDefault. With certain font sizes the text gets clipped. Seems like the algo computes the image width correctly, but chops it when painting. StringFormat.GenericTypographic handles it correctly.

Use Your Own Font Files

Since my hosting company has a poor selection of fonts on their servers (and why would they need more?) I wanted to be able to load a font from a file. It's possible to do it with a PrivateFontCollection object like this:

PrivateFontCollection 
      privateFontCollection = new PrivateFontCollection();

privateFontCollection.AddFontFile (
        ctx.Request.PhysicalApplicationPath + fontFace);

where fontFace is the name of a file with an extension. For example, with the sample code you'll find Book Antiqua in file bkant.ttf. In your HTML you'll specify it as follows:

dotIR.replaceElement (
      "h1", "#990000", "#ccc", 495, "left", 42, "bkant.ttf")

The image handler looks for a font file in the root of your web app.

This is where I hit a snag. What about OpenType fonts? A search in newsgroups revealed that GDI+ doesn't support them. I mean, it does. Partially. It will load a .otf file as long as it has TrueType outlines, not PostScript ones.

What's even stranger GDI does seem to support them, but GDI+ doesn't. The advice I found was to resort to PInvoke to call GDI if you really wanted to load OpenType fonts. At this point I think it will be an overkill, so .ttf files it is for now.

Loaded or Not... Here I Come!

There's a strange issue which I don't know how to deal with. Internet Explorer/Windows thinks it's not done downloading dynamically generated images. Mozilla and Opera "get it" fine. In IE, though, you see a progress indicator on the bottom even though the image handler is done streaming!

This is not the first time I generate images on the fly, but this time around it sure is weird. Every code sample I've seen does it in two lines:

ctx.Response.ContentType = "image/gif";
quantizedBitmap.Save (ctx.Response.OutputStream, ImageFormat.Gif);

Somebody tell me what's missing here. What is IE missing?

Grab It While It's Hot

The source code of beta 2 is available for download. Please let me know of any bugs and suggest improvements.

Comments

Comment permalink 1 Kiliman |
I'm using Fiddler HTTP Debugging Proxy and noticed that the response comes back as "deflate" compressed.

Fiddler has an option to remove the compression. When I clicked it, I got an exeption in Fiddler (as though the data was corrupt, or unexpected).

When I made the request without an Accept-Encoding: header, it rendered just fine.

Anyway, I'm wondering if there's a way for you to disable compression on image files (or at least for this handler) and see if that solves your problem.

I wrote a HTTP Compression filter for ISA 2004, so that's why I'm interested in this.

Kiliman
Comment permalink 2 Milan Negovan |
Interesting... The project I made available for download does not use any compression, yet it suffers from this bug. I even fired up LiveHeaders to make sure nothing was compressed.
Comment permalink 3 Kiliman |
The compression appears to be coming from Blowery.Web.HttpCompress? Here are the response headers I'm getting:

HTTP/1.1 200 OK
Date: Tue, 05 Oct 2004 21:08:02 GMT
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
X-AspNet-Version: 1.1.4322
Content-Encoding: deflate
X-Compressed-By: HttpCompress
Cache-Control: private
Content-Type: image/gif
Content-Length: 3628
Comment permalink 4 Milan Negovan |
Correct, I encrypt pages with Ben's module. I've just now disabled compression on my local copy of the site but IE still shows a progress bar.

Pages don't time out or anything, but it sure is annoying to see that green bar.
Comment permalink 5 Milan Negovan |
I have another dynamic image on this page---the CAPTCHA. It streams just fine. Always did.

I compared headers of them both in Fiddler and LiveHeaders, and they are the same! Strange.
Comment permalink 6 ryan |
How do i compile a web application in c# to allow unsafe code:

for some reason msdn doesn't work for me as i can only get past step 2.

1) Open the project's Property Pages dialog box. For details, see Setting Visual C# Project Properties.
2) Click the Configuration Properties folder.
3) Click the Build property page.
4) Modify the Allow unsafe code blocks property.
Comment permalink 7 Milan Negovan |
Ryan, do you have trouble compiling the .IR project? I don't think I have any unsafe code in there.
Comment permalink 8 ryan |
I got it to compile this morning. I was having trouble compiling because a few of the .cs files did have unsafe code. When I logged in this morning I had no problem.

But... Now.. Everything works except The Imaged Text doesn't load for me. I'm sorry I am a little wet behind the ears. Do I need to install the font on the server or only have the font in the folder of the project?
Comment permalink 9 ryan |
Basically I am getting what is described in your first article:

'If someone disables images you will still see the header text in the image placeholder. The image's alt attribute is what reveals it.'

but... i have images enabled and your online demo works perfectly:
http://www.aspnetresources.com/stuff/irtest.aspx
Comment permalink 10 ryan |
Fixed it. I forgot to include this file: IrImageHanler.ashx

I get the images now, Thank you for sharing!!!!!!
Comment permalink 11 ryan |
One comment: the text wraps seems like the image is a defined width, didn't know if there is an explanation for that or if it can be set as a parameter
Comment permalink 12 ryan |
you may want to delete my comments as I didn't investigate enough before posting, I see the parameter for width. THANKS AGAIN, great article
Comment permalink 13 Joseph Swenson |
View nice! I was struggling to find a way to make this output transparencies and found a good article by Eric W. Bachtal on GDI+ transparencies here :

http://ewbi.blogs.com/develops/2005/08/sparklines_22.html

After a few modifications I was able to make your code output transparencies which is what I needed.
Comment permalink 14 Joseph Swenson |
I think I fixed the IE loading error as well. The javascript in replaceElement() adds "text" to the innerHTML of the node at the end which freaks IE 6 out. If you change the lines to :

if(UA.bIsKHTML){
node.innerHTML += "";
};


Now both Mozilla/Firefox/IE have no problems. Cheers!
Comment permalink 15 Milan Negovan |
Joseph, many thanks for heads-up!
Comment permalink 16 Brett Cooper |
Firstly, fantastic project. I've been looking for something like this for ages.

I've got a particular project where the client is demanding pixel perfect titles (using .IR). Any ideas how you could change the line height for the image/text?
Comment permalink 17 Milan Negovan |
Brett, the height is calculated from font size, which is one of the parameters passed to dotIR.replaceElement.
Comment permalink 18 Brett Cooper |
Sorry, I didn't explain very well.
I am trying to reduce/increase the gap between lines whilst maintaining the same font size.
Comment permalink 19 Milan Negovan |
That's a good question. I've run a search on how to change leading from GDI+, but found very little. It seems there is nothing in GDI+ for that. Some people in the newsgroups suggest calling GDI via P/Invoke.

If someone would like to help and research how to do it, I'll add your solution to the code with all due credit.
Comment permalink 20 lee |
I uploaded the dll to my hosting and i got permission denied due to the unsafe pointer.

How do you guys resolved this with your hosting company?

My hosting company says the dll is requesting a high trust permission. But they only allow medium trust..
Comment permalink 21 matt |
Hi Milan,

.IR is great - thanks!

I was wondering if the images generated would be cached at the server in any way? If not, would it be easy to set IIS up to do so?

Thanks again,
matt
Comment permalink 22 Milan Negovan |
Matt, the images are not cached. This is something I considered at first. However, if you do image replacement on a lot of text, cached bitmaps can bloat memory, so caching needs to be handled with care.

If you wish, you can modify the source code to accommodate caching per your needs.
Comment permalink 23 brian |
Has anyone found a VB solution for this?

I have the javascript from A List apart's version, I just need an aspx file to do the image creation.

Emails and Notifications

Would you like to be notified when somebody responds to this post?  Would you like to have these comments emailed to you?

Submit your comment

Please enter only text since all HTML tags except hyperlinks will be stripped. Hyperlinks will become live links. Any comments with flaming or offensive language will be deleted. Be courteous to other posters. Thank you.

Your name (required):
Your email (optional):
Your site's URL (optional):
Enter this number
Type in the number above:
Comment (required):