Saturday, November 28, 2009

Using the Seadragon control with an Image library

Seadragon is one great control in the Ajax Control Tool kit, if you didn’t see it yet, check it out here, the idea is that it gives you a very cool image viewer to you page with DeepZoom Technology. So high resolution images is not a problem anymore.

Alone the control does everything you need, but it forces you to have your image pre-rendered (with Deep Zoom Composer or any other tool) . So what if we need to make this control work with our image library without creating all Seadragon images (without thinking about maintaining them after updates and moving them to your production nor even the extra size). It’s a small challenge.

  1. So after looking at the problem my approach is as follow:
    Create an Http Handler to handle the requests from the Seadragon Control

    1. For IIS6 register the jpg extension to be handled by our handler
    2. Add the needed configurations to our Web.config
    3. Create our handler
  2. Create the needed Seadragon images on the fly. Providing the needed part only to the control.
    1. Finding the needed level and which part the control need
    2. Getting the high resolution image, making some zooming and slicing
    3. Returning the result to the control by the handler.



Simple, that’s it. We don’t need anything else.

Before going in the details, I only wanted to say that this is only the first phase. For future I will try to make it work with PDF documents with some special zooming tricks. But this is not our problem so let’s go back.
For the first task (1.1) registering the jpg extension in IIS, that’s a simple task
Launch the Internet Services Manager tool, right click on Default Web Site, select Properties, go to Home Directory tab and press Configuration button. This will popup Application Configuration dialog. Click Add button and fill the Executable field with the path to the aspnet_isapi.dll file and fill .jpg in the Extension field. Leave the other fields as is, close the Application Configuration and Default Web Site Properties dialog boxes by pressing the OK button. (if you have IIS7 this step is not needed).


For the task (1.2) Add the needed configurations to our Web.config, add the following:
Add the following in the section of the web config
<httpHandlers>
<add verb="*" path="dzc_output_files/*.jpg" type="ImageHandler.HttpImageHandler,ImageHandler"/>
</httpHandlers>
So now requests coming for the control are captured by our handler, so let’s create it.
For the first task (1.c), Create our handler
using System;

using System;
using System.Web;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Text.RegularExpressions;
using Injazat.DeepZoom;
namespace ImageHandler
{
public class HttpImageHandler : IHttpHandler
{
 
public void ProcessRequest(System.Web.HttpContext context)
{
DynamicDeepZoomImageGenerator DDZIG = new DynamicDeepZoomImageGenerator();
if (context.Request.PhysicalPath.IndexOf(DDZIG.Identifier) < 0)
{
//this is not for the deep zoom images 
context.Response.Clear();
context.Response.ContentType = getContentType(context.Request.PhysicalPath);
context.Response.WriteFile(context.Request.PhysicalPath);
context.Response.End();
}
else
{
context.Response.Clear();
context.Response.ContentType = getContentType(context.Request.PhysicalPath);
byte[] buffer = DDZIG.GeneratorImage(context.Request.PhysicalPath);
context.Response.OutputStream.Write(buffer, 0, buffer.Length);
context.Response.End();
}
}
public bool IsReusable
{
get
{
return false;
}
}
string getContentType(String path)
{
switch (Path.GetExtension(path))
{
case ".bmp": return "Image/bmp";
case ".gif": return "Image/gif";
case ".jpg": return "Image/jpeg";
case ".png": return "Image/png";
default: break;
}
return "";
}
}}
 

So we need now to create the needed Seadragon images on the fly
I created one class to handle that.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Text.RegularExpressions;
 
namespace Injazat.DeepZoom
{
public class DynamicDeepZoomImageGenerator
{
int Size = 250;
public string Identifier = "dzc_output_files";
public DynamicDeepZoomImageGenerator()
{
 
}
public byte[] GeneratorImage(String PhysicalPath)
{
// 1. find the orignal size of the doc
String path = @"C:\inetpub\wwwroot\testthumb\SampleDoc.jpg";
Bitmap imgIn = new Bitmap(path);
Point OriginalSize = new Point(imgIn.Width, imgIn.Height);
 
// 1.5 find number of zoom level avaliable
int ZoomMax = 0;
int X = OriginalSize.Y;
do { X /= 2; ZoomMax++; } while (X > 0);
 
ZoomMax = (int)Math.Ceiling(Math.Log(Math.Max(imgIn.Width, imgIn.Height), 2));
 
// 2. get the zoom level and array location
int ZoomLevel = 0;
Point ArrayLocation = new Point(0, 0);
 
String Pattren = Identifier + @"\\(?<ZoomLevel>[0-9]{1,2})\\(?<XXX>[0-9]{1,2})_(?<YYY>[0-9]{1,2}).jpg";
// use 'x' modifier to ignore comments
Regex r = new Regex(Pattren);
// get the list of group numbers
int[] gnums = r.GetGroupNumbers();
// get first match
Match m = r.Match(PhysicalPath);
if (m.Success)
{
ZoomLevel = int.Parse(m.Groups["ZoomLevel"].Value);
ArrayLocation.X = int.Parse(m.Groups["XXX"].Value);
ArrayLocation.Y = int.Parse(m.Groups["YYY"].Value);
}
// 3. return the needed part depending on the req.
//find the scalling 
int Scale;
if (ZoomMax > ZoomLevel)
{ Scale = (int)Math.Pow(2, (ZoomMax - ZoomLevel)); }
else { Scale = 1; }
 
// resize the image to the scale
Bitmap imgOut = ResizedImage(imgIn, Scale);
 
 
// get only the needed portion of the image
imgOut = GetRegionImage(imgOut, ArrayLocation, Size);
 
return Image2BateArray(imgOut);
}
byte[] Image2BateArray(Bitmap imgIn)
{
System.IO.MemoryStream outStream = new System.IO.MemoryStream();
imgIn.Save(outStream, ImageFormat.Jpeg);
return outStream.ToArray();
}
public Bitmap ResizedImage(Bitmap imgIn, int Scale)
{
if (Scale == 0) Scale = 1;
 
int NewWidth = (int)(1.0F * imgIn.Width / Scale);
int NewHeight = (int)(1.0F * imgIn.Height / Scale);
if (NewWidth == 0) NewWidth = 1;
if (NewHeight == 0) NewHeight = 1;
 
Size NewSize = new System.Drawing.Size(NewWidth, NewHeight);
Bitmap imgOut = new Bitmap(imgIn, NewSize);
return imgOut;
}
Bitmap GetRegionImage(Bitmap imgIn, Point ArrayLocation, int RegionSize)
{
 
Point TopLeft = new Point(ArrayLocation.X * RegionSize, ArrayLocation.Y * RegionSize);
 
Size NewSize = new Size(RegionSize, RegionSize);
if ((TopLeft.X + RegionSize) > imgIn.Width) NewSize.Width = imgIn.Width - TopLeft.X;
if ((TopLeft.Y + RegionSize) > imgIn.Height) NewSize.Height = imgIn.Height - TopLeft.Y;
Rectangle SourceRegion = new Rectangle(TopLeft, NewSize);
 
Bitmap imgOut = new Bitmap(RegionSize, RegionSize);
Graphics g = Graphics.FromImage(imgOut);
g.Clear(Color.White);
 
 
g.DrawImage(imgIn, new Rectangle(0, 0, NewSize.Width, NewSize.Height), SourceRegion, GraphicsUnit.Pixel);
g.Dispose();
return imgOut;
}
void DrawOnImage(ref Bitmap imgIn, String Text)
{
Graphics g = Graphics.FromImage(imgIn);
Font Fo = new Font("Arial", 12);
Rectangle Clip = new Rectangle(0, 0, imgIn.Width, imgIn.Height);
g.DrawString(Text, Fo, Brushes.Blue, Clip);
g.DrawEllipse(Pens.Red, Clip);
Fo.Dispose();
 
g.Dispose();
}
ImageFormat getImageFormat(String path)
{
switch (Path.GetExtension(path))
{
case ".bmp": return ImageFormat.Bmp;
case ".gif": return ImageFormat.Gif;
case ".jpg": return ImageFormat.Jpeg;
case ".png": return ImageFormat.Png;
default: break;
}
return ImageFormat.Jpeg;
}
string getContentType(String path)
{
switch (Path.GetExtension(path))
{
case ".bmp": return "Image/bmp";
case ".gif": return "Image/gif";
case ".jpg": return "Image/jpeg";
case ".png": return "Image/png";
default: break;
}
return "";
}
}
}
So finalizing our work
Create an xml file named “dzc_output.xml”, that will contain:
<?xml version="1.0" encoding="utf-8"?>
<Image TileSize="256" Overlap="0" Format="jpg" ServerFormat="Default" xmlns="http://schemas.microsoft.com/deepzoom/2009">
<Size Width="754" Height="948" /></Image>


Add an web site with a default.aspx page and include the following:
<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>
<cc1:Seadragon ID="Seadragon1" runat="server" SourceUrl="~/dzc_output.xml" 
BorderColor="#660033" BorderStyle="Double" BorderWidth="4px" 
Height="240px" AutoHideControls="False" MaxZoomPixelRatio="10" Width="200px">
</cc1:Seadragon>
In my project I added the handler and the DynamicDeepZoomImageGenerator class to a separate project and referenced it.
That’s it.
You can see from the code that the image loaded is one image , in my next post I will add the needed code not only to change the image, but to extract on from a PDF and display it, and focus on some important locations too.  (It depends on the time :) )


Related Links:

No comments:

Post a Comment