
Introduction
After playing around with Microsoft's Document Imaging Library (MODI) in OCR with Microsoft Office, I decided to add some features to the primary MODI application like scanning, multi TIFF rearrangement and Outlook export. The Outlook export enables you to organize your documents by email folders. Since tools like LookOut, this might be faster than the walk to the good old file cabinet.
Before reading, please note
- You need Office 2003 or Office 2007 to run the application!
- For Office 2007 support see the article OCR with Microsoft Office.
- Office XP does not contain MODI and there is no way to change that!
- For email export, you need MS Outlook 2003 or MS Outlook 2007!
The Application
The application looks very similar to the well known MS Imaging Viewer. The most important difference is the email export to offer a comfortable document storage. From the technical point of view, the application is based on three technologies: MODI, TWAIN and Outlook-Interop (for each of these topics, there is a link at the end of the article which can be used as a tutorial, therefore I won't describe specific details of these techniques). Their integration needed some changes and enhancements and this is what the article is about.

TWAIN Scanning support
First of all, I chose TWAIN support instead of the modern WIA standard because I own a scanner that only supports TWAIN. Based on the very well done code from the Scanning via TWAIN article, I created the TwainControl to encapsulate the scanning functionality. The interaction with the control is done with three methods and one event.
Init and Release
twainControl1.Init(this.Handle);
twainControl1.Release();
Device Selection
The TWAIN library offers a device selection dialog.

This dialog can be shown by calling the following method:
twainControl1.SelectDevice();
Starting the scanning process
The start method has two parameters, bool UI and bool modal. With UI = false, you can start the process without the scanner's configuration dialog. The modal flag controls the modal status of the scan dialog.
twainControl1.StartScanning(UI, modal);
Finishing the scanning process
After the control has proceeded scanning, a FinishScanning event is fired. The main application is registered as a listener.
private void twainControl1_FinishScanning(object sender,
Util.TwainLib.FinishScanningEventArgs e)
{
if (e.scanned)
{
ArrayList images = twainControl1.PopImages();
AppendScannedImages(images);
SaveFile();
}
}
This is the point where integration comes in. The PopImages method writes all scanned pages (if you have a multi page scanner device) into an image array. Afterwards, these images are appended to the MODI document.
Using the scanner button
If you want to open the application by pressing the scanner hardware button, you can add the application path to the Registry. The Registry key in HKEY_LOCAL_MACHINE is Software\Microsoft\Windows\CurrentVersion\StillImage\Registered Applications.
The key value should be:
[Path\]MartinsPaperlessDesktop.exe /StiDevice:%1 /StiEvent:%2
Handling Multi-TIFF files
The first version included self written TIFF handling code (which was not making me happy). By integrating the MODI library, handling multi-TIFF files gets really simple and fast.
Appending Pages
One valuable feature is the 'append'-function which appends pages to a multi-TIFF document. In case that your scanner device does only support single page scanning, this might be helpful.
private void AppendImage(string source)
{
if (_MODIDocument == null) return;
try
{
MODI.Document document = new MODI.Document();
document.Create(source);
_changed = true;
for (int i = 0; i < document.Images.Count; i++)
{
_MODIDocument.Images.Add(document.Images[i],null);
}
}
catch(Exception ee)
{
MessageBox.Show(ee.Message);
SetImage("",false);
}
}
Rearranging Pages
If you got mixed up during the scanning process, you can move single pages within the document to get the order you want.
private void MoveImage(int pageNumber, bool up)
{
if (_MODIDocument == null) return;
MODI.Image img = (MODI.Image) _MODIDocument.Images[pageNumber];
if (up)
{
if (pageNumber-1 >= 0)
{
MODI.Image prevImg =
(MODI.Image) _MODIDocument.Images[pageNumber-1];
_MODIDocument.Images.Add(img,prevImg);
MODI.Image removeImg =
(MODI.Image) _MODIDocument.Images[pageNumber+1];
_MODIDocument.Images.Remove(removeImg);
axMiDocView1.PageNum = pageNumber-1;
}
}
else
{
if (pageNumber+1 < axMiDocView1.NumPages)
{
MODI.Image nextImg = null;
if (pageNumber+2 < _MODIDocument.Images.Count)
{
nextImg =
(MODI.Image) _MODIDocument.Images[pageNumber+2];
}
_MODIDocument.Images.Add(img,nextImg);
MODI.Image removeImg =
(MODI.Image) _MODIDocument.Images[pageNumber];
_MODIDocument.Images.Remove(removeImg);
axMiDocView1.PageNum = pageNumber+1;
}
}
_changed = true;
ShowStatus();
}
OCR and Layout Processing
One primary goal of the layout processing was to keep the original document layout alive. The OCR comes from the MODI.Document.OCR() method. I used the document model from Document Processing Part II to get a better layout serialization than provided by MODI. Since we will export the document's text to an HTML based email, a very trivial HTML converting is done.
private string GetDocumentText()
{
Model.Document doc = Model.Document.CreateByMODI(_MODIDocument);
string c = doc.GetText();
c = c.Replace("\r\n","<\br\>");
return c;
}
MS Outlook Export
Now, the work is all done and exporting is straightforward coding. The source is placed in the DocumentMailer class. The constructor method opens a connection to MS Outlook.
private Microsoft.Office.Interop.Outlook.Application oApp;
private Microsoft.Office.Interop.Outlook._NameSpace oNameSpace;
private Microsoft.Office.Interop.Outlook.MAPIFolder oOutboxFolder;
public DocumentMailer()
{
oApp = new Outlook.Application();
oNameSpace= oApp.GetNamespace("MAPI");
oNameSpace.Logon(null,null,true,true);
oOutboxFolder =
oNameSpace.GetDefaultFolder(OlDefaultFolders.olFolderOutbox);
}
With an opened Outlook connection, the AddToOutBox method does all the work.
Outlook._MailItem oMailItem =
(Outlook._MailItem)oApp.CreateItem(Outlook.OlItemType.olMailItem);
oMailItem.To = toValue;
oMailItem.BodyFormat = Outlook.OlBodyFormat.olFormatHTML;
oMailItem.Subject = subjectValue;
oMailItem.HTMLBody = bodyValue;
oMailItem.SaveSentMessageFolder = oOutboxFolder;
oMailItem.Save();
oMailItem.Display(null);
E-Mail Configuration
To save us from selecting our personal settings each time again, I added a small configuration class. The code is placed in an instance of Configuration which is loaded during initialization.

To have a short look at serialization and deserialization, here is the code for loading:
public static Configuration LoadFromFile(string path)
{
IFormatter formatter = new BinaryFormatter();
Stream streamS =
new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
object o = formatter.Deserialize(streamS);
Configuration solution = (Configuration) o;
streamS.Close();
return solution ;
}
..and for saving as well:
public bool SaveToFile(string path)
{
IFormatter formatter = new BinaryFormatter();
Stream stream =
new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
formatter.Serialize(stream, this);
stream.Close();
return true;
}
The Paperless Desktop - A way to clean up?
After all this technical stuff, we can afford a little distraction. Accidentally I designed a haunting
process oriented adventure.
Players
- The Physical Document or Letter.
- A Desktop Perforator.
- A Clinch Stapler.
- One or more Standard Lever Arch Files.
- A Waste Paper Basket.
- A Scanner.
- Your PC with Office 2003 and Outlook.
- The mighty MartinsPaperlessDesktop Application.
Game instructions:
- Open the physical document ("paper").
- Use envelop with waste paper basket (no, you don't need it for further inquiries).
- Use scanner with document (yes, here we are, back on good old Monkey Island!).
- Use stapler and perforator with letter.
- Use letter with standard lever arch files.
- Use MartinsPaperlessDesktop to perform OCR and E-mail export.
- Forget, that you ever got a physical document. Start believing: "Oh, I got an email."
Sounds too basic to be a five star adventure? Well, you are probably right, but it works for me. Mostly.
It's obvious, the ultimate goal of a paperless desktop is not archived by software. The answer seems to stay in the process. Bringing peace into your desktop's chaos needs discipline from the moment you open the post box. Feel free to share your own experiences in the article's discussion board. That's all for the moment; thanks for reading - and of course, thanks for voting too.
References
Versions
- 3 Apr 2007: Additional information
- 24 Jun 2005: Added search functions
- 12 Jun 2005: Initial version
| You must Sign In to use this message board. |
|
|
 |
 | nice  Md. Marufuzzaman | 1:30 16 Jul '09 |
|
|
 |
|
 |
Good Article.
Been doing some MODI a year ago and today came accross this article, which could of saved me a whole deal of time.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi,
I want like,
On click of thumbanil in left panel, selected image should display at right panel.
Please guide me how should make it.
thanks , dhaval soni
hi, friends
our fields are communication fields ..... ... meet later ...
|
| Sign In·View Thread·PermaLink | 1.00/5 |
|
|
|
 |
|
 |
its so easy.
add one more control of viewer to the left side with different pane.
if (axMiDocView2.NumPages > 0) { if (_MODIDocument1 != null) {
MODI.Image img1 = (MODI.Image)_MODIDocument1.Images[axMiDocView2.PageNum]; sbpBounds.Text = "W:" + img1.PixelWidth.ToString() + " H:" + img1.PixelHeight.ToString(); sbpDPI.Text = "DPI:" + img1.XDPI.ToString() + "/" + img1.YDPI.ToString(); sbpCompression.Text = img1.Compression.ToString(); } } ShowTitle();
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
I installed .Net Framework 3.5 SP1 and Visual Studio 2008 SP1 and the twain init() now always returns Failure. Help
Ronald
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Hi all,
I need to scan a selected region of an image and save it as a tiff file. Can I do it with this library? Is available a source code in C#?
Thanks a lot and regards,
Paolo
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
Martin, Thanks for your excellent article. I have created an empty MODI document and want to add a System.Drawing.Image object to its Images collection (after any necessary conversion, of course). Can you explain how this can be done? I want to avoid the intermediate step of saving the image to disk.
modified on Wednesday, July 16, 2008 1:06 PM
|
| Sign In·View Thread·PermaLink | 1.00/5 |
|
|
|
 |
|
|
 |
|
|
 |
|
|
 |
 | Vista  Ronsch | 5:27 21 Dec '07 |
|
 |
Is this working on Vista?? I heard that Vista uses other libraries/drivers than Twain. If not How to do so?
Ronald
|
| Sign In·View Thread·PermaLink | 1.00/5 |
|
|
|
 |
|
 |
Hi Martin,
Great app and article. I am getting the error below when I click on the "Scan and Create New Document" button. What did I miss in the setup? I can find the tif image of the scanned document in the MartinsPaperlessDesktop\bin\Release\Temp folder so I know the scanning part is creating a new image.
----------------------------------------- An unhandled exception of type 'System.Runtime.InteropServices.COMException' occurred in MartinsPaperlessHome.exe
Additional information: Couldn't create temp dir; check network connect
Thanks,
Patrick
|
| Sign In·View Thread·PermaLink | 4.00/5 |
|
|
|
 |
|
 |
Changed NewDocument method with the following code, it should solve your problem. private void NewDocument() { // create filename with time stamp string TEMP = ApplicationGlobals.GetDocumentPath() + DateTime.Now.Year + DateTime.Now.Month + DateTime.Now.Day + DateTime.Now.Hour + DateTime.Now.Minute + DateTime.Now.Second +".tif";
SetImage("",false); _filename = TEMP; ShowStatus(); }
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Optimized NewDocument method with the following code private void NewDocument() { // create filename with time stamp string TEMP = ApplicationGlobals.GetDocumentPath() + DateTime.Now.ToString("yyyyMMddHHmmss") +".tif";
SetImage("",false); _filename = TEMP; ShowStatus(); }
|
| Sign In·View Thread·PermaLink | 4.00/5 |
|
|
|
 |
|
 |
Does the AxMiDocView have an event for drag and drop (lets say from the desktop)? I see that there is a behavior to “AllowDrop”, but there is no drag-drop event that will allow you to drag an image from the desktop to the screen.
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
I read an article about MODI on the Code Project about a year-and-a-half ago and got very interested. As an Imaging Analyst for several insurance companies I deal with very high volume, automated imaging and mailroom systems with intake from devices such as scanners, fax servers, and ftp sites.
Often faxes arrive warped or up side down for which an operator has to correct and orient the faxes. I employed a server application that uses MODI to straighten and rotate the faxes. It worked well but for only about 1500 faxes. After that MODI just quit! No exceptions, no errors, just no more straightening and rotating. Nothing I did corrected the problem, separate application domains, trying to release the COM object, etc. The only thing that corrected the problem was to stop and restart the server application.
The solution (or rather the work around) was to split the application into two programs. One application runs as a service and starts a second to MODI (straighten and rotate) a single document and then terminate. I used .Net Remoting for communication between the two.
I wanted to get this out and let people know that there may be a problem for long and/or continuous running applications, like services, that use MODI.
T-luv
...don't spit into the wind... Jim Croce
|
| Sign In·View Thread·PermaLink | 3.67/5 |
|
|
|
 |
|
 |
Hi,
This was a great article. Can anyone guide me how to approach the functionality of splitting multi page tiff file. I should be able to know the number of pages in tiff files and specify the range of pages to be splitted and be saved as different tiff file.
Any help will be greatly appreciated
Venkatesh
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
public static List SplitTiffPages(string FileName, BlackWhiteCompression bwComp, ColorCompression cComp) { FileInfo fi = new FileInfo(FileName); List tiffs = new List(); Bitmap bp = null; FrameDimension fd = null; FileStream fs = null;
if (!fi.Exists) throw new Exception("File does not exist.");
try { fs = new FileStream(FileName, FileMode.OpenOrCreate, FileAccess.ReadWrite); bp = new Bitmap(fs); fd = new FrameDimension(bp.FrameDimensionsList[0]);
int pageCount = bp.GetFrameCount(fd);
for (int i = 0; i < pageCount; i++) { FileInfo fiPage = new FileInfo(CreateNewFileName(".tif"));
try { bp.SelectActiveFrame(fd, i); if (bp.HorizontalResolution < 1 || bp.VerticalResolution < 1) bp.SetResolution(200F, 200F); if(bp.PixelFormat== PixelFormat.Format1bppIndexed) bp.Save(fiPage.FullName, GetEncoderInfo("image/tiff"), GetEncoderParameters(EncoderValue.MultiFrame, bwComp)); else bp.Save(fiPage.FullName, GetEncoderInfo("image/tiff"), GetEncoderParameters(EncoderValue.MultiFrame, cComp));
tiffs.Add(fiPage); } catch (Exception ex) { //If an error is thrown thats because the Guid thinks there is a page when there is really no page. if (i == 0) throw new Exception("Tiff file is corrupted.", ex); break; } } } catch (Exception ex) { throw ex; } finally { if (bp != null) { bp.Dispose(); bp = null; } if (fs != null) { fs.Close(); fs.Dispose(); fs = null; } }
return tiffs; }
internal static ImageCodecInfo GetEncoderInfo(String mimeType) { ImageCodecInfo[] encoders; encoders = ImageCodecInfo.GetImageEncoders();
for (int i = 0; i < encoders.Length; i++) { if (String.Compare(mimeType, encoders[i].MimeType, true) == 0) { return encoders[i]; } } return null; }
internal static EncoderParameters GetEncoderParameters(EncoderValue SaveFlag, BlackWhiteCompression Compression) { EncoderParameters enParams = new EncoderParameters(2); enParams.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.SaveFlag, (long)SaveFlag); enParams.Param[1] = new EncoderParameter(System.Drawing.Imaging.Encoder.Compression, (long)Compression); return enParams; }
internal static EncoderParameters GetEncoderParameters(EncoderValue SaveFlag, ColorCompression Compression) { EncoderParameters enParams = new EncoderParameters(2); enParams.Param[0] = new EncoderParameter(System.Drawing.Imaging.Encoder.SaveFlag, (long)SaveFlag); enParams.Param[1] = new EncoderParameter(System.Drawing.Imaging.Encoder.Compression, (long)Compression); return enParams; }
Hope this helps Sully
|
| Sign In·View Thread·PermaLink | 1.00/5 |
|
|
|
 |
|
|
 |
|
 |
Hi,
Thanks for ur great code. While scanning the document, getting an exception "Microsoft cann't handled the exception". This is happening when the Application is Hacking by clicking 'Accept' button.
Could u please guide me, its very urgent.
Regards, Kishore
kishore
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
I registered my application in the registry. [Path\]MartinsPaperlessDesktop.exe /StiDevice:%1 /StiEvent:%2
When I enter into the control panel and try to set my application name for managing the event, it is not displaying my application. Instead it displays only Hp software, Photoshop etc.
Anything should be done in my application?
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
Hi, has anybody tried creating a split screen view, where all of the tiff-s are shown on the left and by clicking on them, they are displayed on the right hand side. Feedback welcomed.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
 |
|
 |
Hello Martin!
It's a really nice Application!
I tried to add an pdf-feature using itextsharp. This code works fine as standalone, but not with paperless desktop.
The Problem is the locked file (ioexception). I've tried to release:
private void DoPDF(string Scanedfile) { ClearStatus(); axMiDocView1.Dispose(); // close view _MODIDocument.Close(false); // close document _filename = null; GC.Collect(); GC.WaitForPendingFinalizers(); ...but here: Bitmap bmp = new Bitmap(Scanedfile);// _filename => ioexeption ...}
Please tell me howto release the Image (perhaps without closing view?)!
best regards,
Michael
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
|