How to Annotate Images from a database in a web page

Use a specialized Image Control to draw text annotations on an image from a database

In your travels as an ASP.NET developer, you may come across the requirement to be able to display an image - usually a TIFF image of some scanned or faxed document, allow the user to make notes and save them directly into the image, and then persist the annotated image, perhaps while also keeping the original untouched image, back to the database.

In order to do this easily we need to have an image control that will accept a bitmap as its image source, rather than the requirement that the image come from a Url. So I resurrected a control that I used to display images in memory from a database in an older article, and adapted it for this purpose.

The key piece of code for this control is it's Bitmap property:

public Bitmap Bitmap
{
get
{
if (HttpContext.Current.Session["persistenceType"] != null) persistenceType = Persistence.Session;
if (PersistenceType == Persistence.Session)
return (Bitmap) Context.Session[String.Concat(CreateUniqueIDString(), "Bitmap")];
else
return (Bitmap) Context.Cache[String.Concat(CreateUniqueIDString(), "Bitmap")];
}
set
{
if (PersistenceType == Persistence.Session)
Context.Session[String.Concat(CreateUniqueIDString(), "Bitmap")] = value;
else
Context.Cache[String.Concat(CreateUniqueIDString(), "Bitmap")] = value;
}
}

The SQL Script provided with the downloadable solution will create a SQL Server IMAGES table, insert a single TIFF image of a letter, and create stored procedures for performing an insert / update and to select an image by Id.

I abstracted all the business logic for the various operations into a separate class "ImageUtils" as static methods for ease of use:

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Web;
namespace Web
{
public class ImageUtils
{
public static Bitmap GetImageFromDb(int id, out int imageId, out string description)
{
SqlConnection cn = new SqlConnection(ConfigurationManager.ConnectionStrings["imagedb"].ConnectionString);
SqlCommand cmd = new SqlCommand("dbo.GetImages");
cmd.Parameters.AddWithValue("@Id", id);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Connection = cn;
SqlDataAdapter da = new SqlDataAdapter(cmd);
DataSet ds = new DataSet();
da.Fill(ds);
DataTable dt = ds.Tables[0];
// if we have an annotated image already in db, use that for display
byte[] imagebytes = (byte[])dt.Rows[0]["OriginalImage"];
if ( dt.Rows[0]["AnnotatedImage"]!=System.DBNull.Value)
//if(imagebytes.Length ==0)
imagebytes = (byte[])dt.Rows[0]["AnnotatedImage"];
HttpContext.Current.Session["originalImage"] = imagebytes;
imageId = Convert.ToInt32(dt.Rows[0]["Id"]);
description = Convert.ToString(dt.Rows[0]["Description"]);
MemoryStream ms = new MemoryStream(imagebytes);
Bitmap bmp = (Bitmap)Image.FromStream(ms);
return bmp;
}
public static bool UpdateImageData(Bitmap originalImage, Bitmap annotatedImage, string description, int id)
{
SqlConnection cn = new SqlConnection(ConfigurationManager.ConnectionStrings["imagedb"].ConnectionString);
SqlCommand cmd = new SqlCommand("dbo.InsertOrUpdateImage");
byte[] originalImageBytes = (byte[]) HttpContext.Current.Session["originalImage"];
cmd.Parameters.AddWithValue("@OriginalImage",originalImageBytes );
MemoryStream ms = new MemoryStream();
annotatedImage.Save(ms, ImageFormat.Tiff);
cmd.Parameters.AddWithValue("@AnnotatedImage", ms.ToArray());
cmd.Parameters.AddWithValue("@Description", description);
cmd.Parameters.AddWithValue("@Id", id);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Connection = cn;
cn.Open();
int ret = cmd.ExecuteNonQuery();
cmd.Dispose();
cn.Close();
if (ret > 0)
return true;
else
return false;
}
public static Bitmap AnnotateImage(Image myImage,string Message)
{
int len = Message.Length;
int units = len / 15; // use this to scale vertical size of rectangle
Color FillColor = Color.FromArgb(75, 255, 255, 255);
var FillBrush = new SolidBrush(FillColor);
var FillRectangle = new Rectangle(5, 5, 410, 50 * units);
var TextFont = new Font("Arial", 12);
var TextBrush = new SolidBrush(Color.Navy);
var TextFormat = new StringFormat();
TextFormat.Alignment = StringAlignment.Near;
TextFormat.LineAlignment = StringAlignment.Near;
// convert TIFF file to jpg (non-indexed) format so we can "Write on it"
var ms = new MemoryStream();
myImage.Save(ms, ImageFormat.Jpeg);
myImage = Image.FromStream(ms);
Graphics DrawingSurface = Graphics.FromImage(myImage);
DrawingSurface.FillRectangle(FillBrush, FillRectangle);
DrawingSurface.DrawString(Message, TextFont, TextBrush, FillRectangle, TextFormat);
return (Bitmap)myImage;
}
public static int UploadImage(string description,string fileName, byte[] imageBytes)
{
SqlConnection cn = new SqlConnection(ConfigurationManager.ConnectionStrings["imagedb"].ConnectionString);
SqlCommand cmd = new SqlCommand("dbo.InsertOrUpdateImage");
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("@OriginalImage", imageBytes);
cmd.Parameters.AddWithValue("@AnnotatedImage", new Byte[0]);
cmd.Parameters.AddWithValue("@Description", description);
cmd.Parameters.AddWithValue("@Id", null);
object o = null;
try
{
cn.Open();
cmd.Connection = cn;
o = cmd.ExecuteScalar();
}
catch
{
throw;
}
finally
{
cmd.Dispose();
cn.Close();
}
return (int) o;
}
}
}

You can see above that the way I size and position the Rectangle to hold the text is pretty simplistic. For a more sophisticated approach, you would probably want to parameterize this process and do measurements. I also provide a utility page that allows you to upload an image from the file system into the database:

string description = this.TextBox1.Text;
string filename = this.FileUpload1.FileName;
byte[] imageBytes = this.FileUpload1.FileBytes;
this.TextBox1.Text = ImageUtils.UploadImage(description, filename, imageBytes).ToString();

The key code in the main display / annotate page codebehind is as follows:

public partial class WebForm1 : Page
{

private string FileName = "LETTER.TIF";
private int imageId = 0;
private string description = String.Empty;

protected void Page_Load(object sender, EventArgs e)
{
// original code (without database)
//ImageControl1.Bitmap = (Bitmap) Image.FromFile(Server.MapPath("images") + "\\" + FileName);
// ImageControl1.PersistenceType = Persistence.Session;
if (!IsPostBack)
{
ImageControl1.Bitmap = ImageUtils.GetImageFromDb(1, out imageId, out description);
this.lblDescription.Text = description;
}
}
protected void Button1_Click(object sender, EventArgs e)
{
//annotate the image and redisplay
Image myImage = ImageControl1.Bitmap;
String Message = TextBox1.Text;
ImageControl1.Bitmap = ImageUtils.AnnotateImage(myImage, Message);
}
#region Web Form Designer generated code
protected override void OnInit(EventArgs e)
{

InitializeComponent();
base.OnInit(e);
}
private void InitializeComponent()
{

}
#endregion

protected void Button2_Click(object sender, EventArgs e)
{
// save image to database
bool retval= ImageUtils.UpdateImageData(null, ImageControl1.Bitmap, this.TextBox1.Text, imageId);
}
}

An important part of the process is that we must convert a TIFF image to a non-indexed format (JPEG) in order to draw on it. When we are done it is saved back to the database in TIFF format again. A Sample annotated image looks like this:





You can download the complete Visual Studio 2008 Solution here.

By Peter Bromberg   Popularity  (5385 Views)