Click here to Skip to main content
Click here to Skip to main content

TIFF Editing Made Simple

, 24 Mar 2008 CPOL
Quickly and easily create an application to split, merge, and reorder TIFFs – sample application and source code is provided.

Editorial Note

This article is in the Product Showcase section for our sponsors at CodeProject. These reviews are intended to provide you with information on products and services that we consider useful and of value to developers.

Create an Application to Split, Merge, and Reorder TIFFs in a Few Minutes – Source Code and EXE Provided

At Atalasoft, we often look at imaging tasks that our customers are trying to perform and evaluate whether or not we are presenting them in the most straightforward way. It’s a challenge as a core engineer and architect because I’ve had a specialized and very intimate view of our technologies. I need to periodically step back and shed my point of view and try to adopt the point of view of our customers.

TIFF technologies are a perfect example. Atalasoft has a full suite of tools for reading, creating and manipulating TIFF files in very minute detail. It is possible to do most anything to TIFF files, provided you know and understand how TIFF files are put together. Unfortunately, if you don’t understand how TIFF files are assembled, this code also allows you to create invalid or non-standard TIFF files.

Our customers frequently want to do much more high-level operations on TIFF file elements, including merging files, reordering images within files, removing images and so on. While adding this functionality is possible with our existing classes, there are a number of gotchas, not the least of which is resource management. For example, if you read all the pages in for re-saving into another file, you risk running out of memory. In addition, you will be re-encoding your images and if they are compressed with a lossy compression scheme (like JPEG), the image quality will degrade.

In DotImage 6.0, we’ve added in three new classes that make high-level document manipulation trivial. The classes are:

  • TiffDocument
  • TiffPageCollection
  • TiffPage

The model works like this — a TiffDocument object is constructed from a stream (or a file name). In construction, TiffDocument uses the stream to populate a TiffPageCollection with TiffPage objects, each of which represents a page within the TIFF file.

TiffPage objects are abstract representations of the actual pages. They are populated with information about the pages that you may care about, like page size, pixel format, and resolution. The actual image data is never needed so it is ignored. In testing with TIFF files composed of thousands of pages, we can populate the collection at a rate of only a few milliseconds per page.

To reorder the pages in a TiffDocument, you reorder the elements of the TiffPageCollection available through the Pages property then call the Save method.

To remove the pages in a TiffDocument, you delete elements from the Pages property then call the Save method.

To insert a page in a TiffDocument you can a TiffPage from any other TiffDocument’s Pages property. In addition, we have a convenience constructor that lets you make an TiffPage object from an AtalaImage directly — note that this will make an in-memory copy of the image, allowing you to further manipulate or even dispose the original AtalaImage without affecting your TiffPage.

The simplicity and flexibility of this model is immediately apparent. Atalasoft has added a utility method called Combine which will combine an arbitrary set of TIFF Streams into a single output stream. Removing the argument and error checking, the implementation of Combine looks like this:

public static void Combine(Stream output, params Stream[] sources)
{
    // argument checking removed

    TiffDocument doc = new TiffDocument(sources[0]);
    for (int i=1; i < sources.Length; i++)
    {
        TiffDocument combineDoc = new TiffDocument(sources[i]);
        doc.Pages.Add(combineDoc.Pages);
    }
    doc.Save(output);
}

The code simply makes a TiffDocument for the first element of the sources array, then for each of the remaining sources the code makes a TiffDocument and adds the pages to the first before saving the document.

To test out the utility of the overall suite of classes, I set myself the task of writing an application that would allow me to directly manipulate TIFF files by dragging and dropping thumbnail images of the pages and to delete pages by selecting the pages I didn’t want by hitting the delete or backspace key.

I built the application in Visual Studio 2003 in C# as a standard Windows Forms application. The main window includes an Atalasoft ThumbnailView control to show the thumbnails and a ListBox to show all the file names that are part of your work.

image001.jpg

Figure 1 – Atalasoft Tiff Manipulator User Interface

To assist my efforts I created a struct called FileFramePair that looks like this:

public struct FileFramePair
{
      private string _fileName;
      private int _frame;
 
      public FileFramePair(string fileName, int frame)
      {
            _fileName = fileName;
            _frame = frame;
      }


      public string FileName { get { return _fileName; } set { _fileName = value; } }
      public int Frame { get { return _frame; } set { _frame = value; } }
      public override string ToString()
      {
            return Path.GetFileName(_fileName);
      }
}

This struct is an aggregation of a file name and a frame number. I’ve also added a ToString method so that putting it into UI elements will generate reasonable output. This struct is set as the Tag property of an item in the ThumbnailView. This makes is easy to know which file and frame I’m looking at for any given ThumbnailView item. In addition, I also put the struct in the list of file objects. I don’t really need to use the struct there since I don’t need the Frame element, but it is convenient.

Here is the code that adds all the frames of a TIFF file into the ThumbnailView:

private void OnFileAdded(string fileName)
{
      try
      {
            ImageInfo info = RegisteredDecoders.GetImageInfo(fileName);
            int offset = thumbnailView1.Items.Count;
            for (int i=0; i < info.FrameCount; i++)
            {
                  FileFramePair pair = new FileFramePair(fileName, i);
                  thumbnailView1.Items.Add(fileName, i, pair.ToString() + "\nPage " + i);
                  thumbnailView1.Items[offset + i].Tag = pair;
            }
      }
      catch
      {
      }
      fileList.Items.Add(new FileFramePair(fileName, 0));
}

Using the ImageInfo object, we determine how many frames there are in the image, then for each frame, we make a FileFramePair and add a new item to the ThumbnailView. This will automatically create the thumbnail image and add it to the view. The FileFramePair is then set to the new item’s Tag property so we can easily get it back later. Note that the label we use on the thumbnail is built from the FileFramePair’s ToString() method which will return the file portion of the full path. Finally, we add an item to the fileList list box. Again we use a FileFramePair because it is convenient and already has a useable ToString() override.

All the actual manipulation of pages, including reordering and deleting is handled by the ThumbnailView. There is a little UI glue code, but nothing outrageous. For saving, the code makes an array of TiffDocument objects with one entry for every file represented in the fileList. This is array is used when building the final document. To build the final TiffDocument, we construct a new empty TiffDocument, then for each thumbnail look up the appropriate TiffDocument from the fileList and add the appropriate page from it into the final document.

The code to save the final document looks like this:

private void SaveTo(string fileName)
{
      // get a doc for the originals
      TiffDocument[] originals = MakeTiffDocuments();
      TiffDocument final = new TiffDocument();
      try
      {
            // build final from the thumb's Tag object
            foreach (Thumbnail thumb in thumbnailView1.Items)
            {
                  FileFramePair fileFrame = (FileFramePair)thumb.Tag;
                  TiffDocument original = GetTiffDocument(fileFrame.FileName, originals);
                  if (original == null)
                  {
                        MessageBox.Show(
                            "Unable to find the original source file " +
                            fileFrame.ToString() + 
                            " - this should never happen - skipping file.");
                        continue;
                  }
                  final.Pages.Add(original.Pages[fileFrame.Frame]);
            }
            using (FileStream stm = new FileStream(fileName, FileMode.Create))
            {
                  final.Save(stm);
            }
      }
      finally
      {
            // close the original stream
            foreach (TiffDocument doc in originals)
            {
                  doc.Pages[0].Stream.Close();
            }
      }
}

The only thing that is “funny” is that before exiting, I go in and close the stream from page 0 of each document in the originals list. I toyed with the idea of making TiffDocument and TiffPage implement IDisposable — but resource issues arise. If I remove a page from one TiffDocument and put it into another, then dispose the original document (or let the garbage collector take it), this will make the TiffPage object that we moved instantly invalid. Similarly, if TiffPage implements IDisposable and I remove a TiffPage from a TiffDocument, when the removed TiffPage is disposed or collected, the entire TiffDocument becomes invalid. It seems a reasonable concession to put the onus of releasing the resource onto the client code. If you forget to close the streams, when the stream objects themselves are garbage collected, they will self-close.

There is another case that you need to avoid. Consider the following innocuous looking method:

public TiffDocument OpenFromFile(string filename)
{
      using (FileStream stm = new FileStream(filename, FileMode.Open))
      {
            return new TiffDocument(stm);
      }
}

Upon exiting the using block, the stream will be disposed, which will make the TiffDocument invalid. TiffDocument comes with a filename based constructor which should be used instead in the previous snippet:

public TiffDocument OpenFromFile(string filename)
{
      return new TiffDocument(filename);
}

In the application, the only remaining holes are the utility methods MakeTiffDocuments() and GetTiffDocument(). MakeTiffDocuments(), as described earlier, makes a TiffDocument object for every file in the file list:

private TiffDocument[] MakeTiffDocuments()
{
    TiffDocument[] arr = new
    TiffDocument[fileList.Items.Count];

    for (int i=0; i < arr.Length; i++)
    {
        FileFramePair pair = (FileFramePair)fileList.Items[i];
        arr[i] = new TiffDocument(pair.FileName);
    }
    return arr;


}

All that happens is that we make a TiffDocument object from the file name in each FileFramePair in the list — no rocket science here.

Finally, GetTiffDocument() is a utility routine that, given a file name, returns the appropriate TiffDocument from the array returned by MakeTiffDocuments:

private TiffDocument GetTiffDocument(string fileName, TiffDocument[] originals)
{
    for (int i=0; i < fileList.Items.Count; i++)
    {
        if (((FileFramePair)fileList.Items[i]).FileName == fileName)
        {
            return originals[i];
        }
    }
    return null;
}

As a sanity check, we return null if we can’t find the original file. Clearly this should never happen within the context of this application, but it would be good form to return an easily detected value on failure.

As illustrated with the new Atalasoft Tiff classes, it becomes trivial to write code to perform common high-level operations on TIFF files. As you write your application, you will discover that you are free to concentrate on your application and its user interface instead of having to memorize the TIFF specification. This application and its source code are part of the DotImage 6.0 SDK, available in a free download evaluation from Atalasoft's website. More information about Atalasoft's TIFF Codec and TIFF viewing/annotation is available.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Steve Hawley
Architect Atalasoft, a Kofax Company
United States United States
Steve Hawley is a software engineer/architect at Atalasoft, Inc., responsible for current and future component designs.
Follow on   Twitter

Comments and Discussions

 
GeneralWeb App Tiff Manipulator Pinmemberquochle21-May-08 9:30 
GeneralSteve PinmemberDoug DeBug9-Apr-08 10:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.141223.1 | Last Updated 24 Mar 2008
Article Copyright 2008 by Steve Hawley
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid