Working with TIFF Images

Tag Image File Format (TIFF) files are used for a diverse set of applications such as GIS (geographic information systems), CAD drawing programs, graphic arts, and so forth. Fortunately, a portable library is freely available, from www.libtiff.org, for decoding the information stored in TIFF files. The library also provides software tools that can be extremely helpful for common graphics programming tasks. The rest of this article takes you through the steps required to incorporate the TIFF library into your C++ projects.

JPEG images need no introduction due to their widespread use on the Web, but let's suppose you are a poor student and you don't want to pay for an image library; what other possible avenues exist for supporting JPEG images? The answer: another free library available publicly on the Web. This article just wouldn't be complete without incorporating a free JPEG image library.

If you've been working with Windows for a while, you probably have lots of BMP files on your hard drive. Working with BMP files has been well covered on CodeGuru and I see little advantage to providing another implementation. On the other hand, it would be nice to have a complete image library for Windows and it can be frustrating when an article almost covers a topic but leaves out one important aspect. So, I'll include a brief section on BMP files as well.

The original article was titled "Working with TIFF Images" and the TIFF library is really the motivation for re-writing that article. Of course, there are always comments such as "Why don't you support format X?" so this revision includes support for more formats, but the TIFF library has really come a long way, especially in the area of the tools that are included with the library and extensions to the library. Unfortunately, there is not enough time to go into each of the TIFF tools, so only the PDF conversion tool is highlighted. A particular extension to the TIFF library will be covered shortly with an article on GeoTIFF images. GeoTIFFs are geographically referenced images, typically the result of a satellite or aerial photo. Stay tuned for more on handling GeoTIFF files.

So, this article covers the following topics:

  1. Working with the TIFF image library
    1. Building the TIFF library
    2. Loading a TIFF image into a DIB (device independent bitmap)
    3. Writing a DIB image to a TIFF image file
    4. Converting TIFF images to PDF (portable document format) files
  2. Working with the JPEG image library
    1. Building the JPEG image library
    2. Loading a JPEG image file into a DIB
    3. Writing a DIB image to a JPEG image file
    4. Using the TIFF library to convert a JPEG image to PDF format
  3. Working with BMP images
    1. Loading a BMP image file into a DIB
    2. Writing a DIB image to a BMP file
  4. Additional information about the sample project

Working with the TIFF Image Library

Building the TIFF image library

If you download the TIFF library from www.libtiff.org, you will have to spend a little time dealing with make files; however, my sample project already has the VC++ project files set up. Using the project files provided in the sample means that you may have to make modifications when new versions of the library are released. The library was written in C and contains all the code necessary to encode and decode TIFF image files. For an application programmer, all that is necessary is to call the appropriate functions from the library.

So, before working with the sample application, open the provided project files for libtiff and tiff2pdf and build them: first libtiff, and then tiff2pdf. For the libtiff project, build both the debug and release mode versions. The debug build produces libtiffd.lib and the release build produces libtiff.lib. For the tiff2pdf project, you may build either the debug or the release version, but there is no need to build both. Once these projects have been built, move the tiff2pdf.exe file from its release folder to the application folder. The application process will pull in the files as needed. Here is the folder structure for the libraries and application:

If you build from the makefiles provided by www.libtiff.org, just remember to build a debug static library called "libtiffd.lib" and a release static library called "libtiff.lib" and place those libraries in the "libs" folder ("libs" is on the same level as VC6).

Loading a TIFF image into a device independent bitmap

The logic necessary to load a TIFF image from a file into a DIB is trivial due to the presence of the TIFFReadRGBAImage library function. This function does the work of converting the underlying file format into a rectangular RGBA block of data. It's tempting to say, okay we're done. But that would be selling the library short. Another property of TIFF files is that they can contain more than one image, so a single TIFF file can actually behave like an archive for numerous images. Typically, this may be used when someone has several related images (for example, blueprints showing different angles of a single structure). In that case, it might be more convenient to save them all to a single TIFF file. If you ignore the directory structure of a TIFF file and simply open the file and use the functions to read the data, all that will happen is you get the first image. In most cases, the first image is the only image, but sometimes there are more. The following outline describes the process of reading the first image from the image file. The project contains more code for handling cases easily where a TIFF image file contains multiple images.

  1. Use the "TIFFOpen" call, passing in a filename and io mode.
  2. Use the "TIFFGetField" function to read the image height and width tags, and then allocate the required space (WxHx4).
  3. Use "TIFFReadRGBAImage" to copy the actual pixel data from the file to the space you just allocated.
  4. Copy the raster data to a Device Independent Bitmap (DIB), using care to maintain the picture layout.
  5. Delete the allocated space for the pixel data.
  6. Call "TIFFClose" to free resources allocated in the library.
  7. Draw the DIB to the screen.

The following code sample implements the approach outlined above:

void TIFFTestDoc::OnFileOpen()
{
    CString file_types = 
       "TIFF Files(*.tiff,*.tif)|*.tif;*.tiff;*.tif;||";

      // create the file open dialog
    CFileDialog dlg(TRUE,
                    NULL,
                    NULL,
                    OFN_HIDEREADONLY | OFN_EXPLORER,
                    (LPCTSTR)file_types,NULL);

       // show the file open dialog
    if (dlg.DoModal() == IDOK)
    {
         // get the selected file from the file-open dialog
        m_filename = dlg.GetPathName();

        // 1) Opening the file
        TIFF * tiff = TIFFOpen((char*)(LPCTSTR)m_filename,"r");
        if (tiff)
        {

            int w=0, h=0;
            // 2)
            // Get the width and height of the image

            TIFFGetField(tiff,TIFFTAG_IMAGEWIDTH, &w);

            TIFFGetField(tiff,TIFFTAG_IMAGELENGTH, &h);

            if ((w > 0) && (h > 0))
            {
              // allocate space for the image

              uint32 * raster =
                  (uint32*)GlobalAlloc(GMEM_FIXED,
                                       (w * h * sizeof (uint32)));

              if (raster)
              {
                // creating DIBSection object that encapsulates
                // DIB functionality

                if (m_dib)
                    delete m_dib;

                 m_dib = new DIBSection;

                 if (m_dib)
                 {
                     m_dib->Create(w,h,32);
                       
                     uint32 dibwidth = m_dib->GetTotalWidth();

                     // 3)
                     // copy all the pixel data from the file into
                     // allocated space

                     if (TIFFReadRGBAImage(tiff, w, h, raster, 0))
                     {
                        // 4)
                        // its tempting to copy straight to the DIB,
                        // however the DIB has an alignment
                        // restriction that is not applicable to 
                        // tiff files...so they may have different
                        // widths

                        unsigned long * dest =
                             (unsigned long *)m_dib->GetBits();

                        unsigned long * src =
                             (unsigned long *)raster;

                        for (int row = 0; row < h; row++){

                           void * ptr_dest = dest + row * dibwidth;
                           void * ptr_src = src + row * w;
                           memcpy(ptr_dest,ptr_src,w*sizeof(int));
                        }
                        SetTitle(m_filename);
                        UpdateAllViews(NULL);
                     }
                  }
                  // 5)
                  // free the temporary pixel storage space
                  GlobalFree(raster);
               }
            }
            // 6)
            // notify the TIFF library that we are done
            // with this puppy
            TIFFClose(tiff);
        }
    }
}

void TIFFTestView::OnDraw(CDC* pDC)
{
    TIFFTestDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);

    DIBSection * dib = pDoc->GetDIB();

    if (pDC->IsPrinting())
    {}
    else
    {
        if (dib)
        {
            CClientDC dc(this);
            CPoint sp = GetScrollPosition();
            dib->Draw(&dc,sp.x,sp.y);
        }
    }
}

Writing DIBs to TIFF image files

Saving a DIB image to a TIFF file is essentially the reverse of opening an image file. The following code snippet provides a simplistic implementation of the process.

int SaveDIB2TIFF(const char * output_file, DIBSection& dib)
{
    int result = 0;

        // get the image information from the provided DIB
        UINT32 w = dib.Width();
        UINT32 h = dib.Height();
        UINT32 total_width = dib.GetTotalWidth();
        UINT32 bitcount = dib.GetBitCount();
        UINT32 bytecount = bitcount / 8;

        if (dib.IsCreated() && (w > 0) && (h > 0))
        {
            double image_gamma = TIFF_GAMMA;

            TIFF * tif;

            // open the output TIFF image for writing
            if ((tif = TIFFOpen(output_file, "w")) == NULL)
            {
                return result;
        }

        // set up the image tags
        TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, w);
        TIFFSetField(tif, TIFFTAG_IMAGELENGTH, h);
        TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8);
        TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_PACKBITS);
        TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB);
        TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, 3);
        TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, 1);
        TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
        TIFFSetField(tif, TIFFTAG_RESOLUTIONUNIT, RESUNIT_NONE);
        TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);

        unsigned char * psrc = (unsigned char *)dib.GetBits();
        unsigned char * pdst = new unsigned char[(w * 3)];

        UINT32 src_index;
        UINT32 dst_index;

        // now go line by line to write out the image data
        for (int row = 0; row < h; row++ ){

            // initialize the scan line to zero
            memset(pdst,0,(size_t)(w * 3));

            // moving the data from the dib to a row structure that
            // can be used by the tiff library
            for (int col = 0; col < w; col++){
                src_index = (h - row - 1) * total_width * bytecount
                                          + col * bytecount;
                dst_index = col * 3;
                pdst[dst_index++] = psrc[src_index+2];
                pdst[dst_index++] = psrc[src_index+1];
                pdst[dst_index] = psrc[src_index];
                result++;
            }

            // now actually write the row data
            TIFFWriteScanline(tif, pdst, row, 0);
        }

        TIFFClose(tif);
    }

    return result;
}

Converting TIFF images to PDF (portable document format) files

The TIFF library comes with a convenient tool for converting TIFF images into PDF format. Because we now have the ability to save a DIB image as a TIFF file we can essentially convert any image into a PDF file. This fantastic capability is accomplished by another C function provided with the TIFF library called tiff2pdf. For the sample project, I choose to build the tiff2pdf function into a separate executable and launch the executable from the application.

Working with TIFF Images

Working with the JPEG Image Library

Building the JPEG image library

The JPEG image library is freely available from www.ijg.org. As with the TIFF library, the sample project has provided VC++ project files to build the libraries, so there is no need to download the library. In the sample project, the JPEG library is located under the libs folder and the build produces ijgd.lib and ijg.lib for the debug and release modes, respectively.

Loading a JPEG image file into a DIB

Loading a JPEG image from the library is similar to the TIFF case. The file is opened and some preliminary information is read; then the actual image data is loaded. The following code snippet illustrates the example.

int OpenIJG2DIB(const char * filename, DIBSection& dib)
{
    int result = 0;

    struct jpeg_decompress_struct cinfo;
    struct my_error_mgr jerr;

    FILE * infile;
    JSAMPARRAY buffer;
    int row_stride;

    if ((infile = fopen(filename, "rb")) == NULL)
    {
        return result;
    }

    cinfo.err = jpeg_std_error(&jerr.pub);
    jerr.pub.error_exit = my_error_exit;

    if (setjmp(jerr.setjmp_buffer))
    {
        jpeg_destroy_decompress(&cinfo);
        fclose(infile);
        return result;
    }

    jpeg_create_decompress(&cinfo);

    jpeg_stdio_src(&cinfo, infile);

    jpeg_read_header(&cinfo, TRUE);

    jpeg_start_decompress(&cinfo);

    if (cinfo.num_components == 3)
    {
        dib.Create(cinfo.image_width, cinfo.image_height, 24);
        if (dib.IsCreated())
        {
            row_stride = cinfo.output_width * cinfo.output_components;

            buffer = (*cinfo.mem->alloc_sarray)
                ((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);

            unsigned char * dibBits = (unsigned char *)dib.GetBits();
            UINT32 off;
            UINT32 tw = dib.GetTotalWidth();
            UINT32 height = dib.Height();
            UINT32 imgWidth = dib.Width() * 3;
            UINT32 col;

            UINT32 row = 0;

            while (cinfo.output_scanline < cinfo.output_height){
                // pull the scanline out of the file and into
                // the buffer
                jpeg_read_scanlines(&cinfo, buffer, 1);

                // swap all the red and blue pixels
                for (col=0;col<imgWidth;col+=3){
                    SWAP(buffer[0][col],buffer[0][col+2]);
                }

                // copy the row over to the appropriate place in the
                // output dib
                off = (height - ++row) * 3 * tw;
                memcpy(&dibBits[off],buffer[0],row_stride);
            }
        }

        result++;
    }

    jpeg_finish_decompress(&cinfo);

    jpeg_destroy_decompress(&cinfo);

    fclose(infile);

    return result;
}

Writing a DIB image to a JPEG image file

Writing DIB image data to a JPEG image file is very straightforward using the Independent JPEG Group's image library. Similar to the TIFF approach, the process consists of opening a file for writing, setting some values that define the image, and writing the image data to the file. The following code snippet outlines the approach.

int SaveDIB2IJG(const char * filename, DIBSection& dib,
                INT32 quality)
{
    int result = 0;

    if (dib.IsCreated() && (dib.GetBitCount() == 24))
    {
        struct jpeg_compress_struct cinfo;
        struct jpeg_error_mgr jerr;

        FILE * outfile;
        JSAMPROW row_pointer[1];
        int row_stride;

        memset(&cinfo,0,sizeof(jpeg_compress_struct));

        cinfo.err = jpeg_std_error(&jerr);
        jpeg_create_compress(&cinfo);

        if ((outfile = fopen(filename, "wb")) == NULL)
        {
            return result;
        }

        jpeg_stdio_dest(&cinfo, outfile);

        cinfo.image_width = dib.Width();
        cinfo.image_height = dib.Height();
        cinfo.input_components = 3;
        cinfo.in_color_space = JCS_RGB;

        jpeg_set_defaults(&cinfo);

        jpeg_set_quality(&cinfo, quality, TRUE);

        jpeg_start_compress(&cinfo, TRUE);

        row_stride = cinfo.image_width * 3;

        unsigned char * dibBits = (unsigned char *)dib.GetBits();
        UINT32 off;
        UINT32 tw = dib.GetTotalWidth();
        UINT32 height = dib.Height();
        UINT32 imgWidth = dib.Width() * 3;
        UINT32 col;

        UINT32 row = 0;

        // for each scan line of the image

        while (cinfo.next_scanline < cinfo.image_height){
            // copy the row over to the appropriate place in the
            // output dib
            off = (height - ++row) * 3 * tw;
            row_pointer[0] = &dibBits[off];

            // swap all the red and blue pixels
            for (col=0;col<imgWidth;col+=3){
                SWAP(row_pointer[0][col],row_pointer[0][col+2]);
            }

           jpeg_write_scanlines(&cinfo, row_pointer, 1);
        }

        jpeg_finish_compress(&cinfo);

        fclose(outfile);
        jpeg_destroy_compress(&cinfo);
    }

    return result;
}

Converting JPEG images to PDF (portable document format) files

Converting a JPEG image to a PDF file is just a simple, two-step process based on the results in the TIFF section. Because we can only convert actual TIFF image files into PDF files, we simply need to open a JPEG file into a DIB, save the DIB to a TIFF file, and then launch tiff2pdf. The command line for the tiff2pdf executable is: tiff2pdf.exe -o <new pdf file name> <existing TIFF image file name>

Working with TIFF Images

Working with BMP Images

BMP image files are so simple that a separate library is really not necessary. The only complication that comes with BMP files is that there are many variants of the format, with and without compression, but the variants and the compression schemes are very simple. The following table shows the basic layout of a BMP file.

File Header (14 bytes)
Image Header (40 bytes)
Palette (optional)
Image Data

Loading a BMP image file into a DIB

The decoding logic makes use of two fields within the image header: the bit count and the bit compression. The decoding is a rather lengthy chunk of code that is contained within the sample project, but the following code snippet just shows the cases handled by the decoder without the bulky implementation. Keep in mind that the bmInfo structure is the image header object.

switch (bmInfo.bmiHeader.biBitCount){
    case 1:
    {
        // no compression
        // palette is provided with 2 colors; an image value of 0
        // means first color, 1 means second color in the palette
    }
    break;
    case 4:
    {
    if (bmInfo.bmiHeader.biCompression == BI_RGB)
    {
        // palette is provided and image data value are actually
        // index values into the palette data
    }
    else if (bmInfo.bmiHeader.biCompression == BI_RLE4)
    {
        // 4-bit run-length compression scheme using up to 16 colors
        }
    }
    break;
    case 8:
    {
    switch (bmInfo.bmiHeader.biCompression){
        case BI_RGB:
        {
            // no compression - uses 8 bit (256 colors) palette
        }
        break;
        case BI_RLE8:
        {
            // 8-bit run-length compression scheme using 256 colors
            // (8-bit)
        }
        break;
        }
    }
    break;
    case 24:
    {
        // no compression scheme and no palette. image data values
        // break down as 8 bits for red, followed by 8 bits for
        // green, followed by 8 bits for blue
    }
    break;
}

Writing a DIB image to a BMP image file

Today, there really isn't much reason to write out BMP files that use compression of less than 24 bits per pixel. Data storage capacity is not as expensive as it was when the BMP format was developed. And, if you want to compress an image, you will probably use JPEG or some kind of wavelet transform that is far superior to run-length encoding. This is my justification for only saving DIB images to 24-bit BMP formats with no compression. If you want lossless compression on a BMP image, just use WinZip.

Additional Information about the Sample Project

Due to file size limitations on the CodeGuru servers, I had to split the sample project up into three separate downloads. The JPEG and TIFF library downloads have just enough code to build the libraries as needed for the sample project; the library downloads are not the full version provided by www.ijg.org and www.ijg.org, respectively. The sample application download contains some re-useable common code plus the application code. Unzipping the three download files to the same sample folder should insure that your sample folder structure is the same as the image at the beginning of the article. Running the TIFFSample application is simple; just click the File menu, and then Open and select a JPG, TIF, or BMP file from your file system. Converting any image to PDF will save the PDF file in the sample application folder.

The libtiff.org website has some sample TIFF images that you can download if you want.



About the Author

Andy McGovern

Andy McGovern - Software Developer/Engineer. Special interests: astronomy, image processing, orbital mechanics, mathematics. Currently work at the Johns Hopkins University Applied Physics Laboratory on the science operations team for the CRISM instrument on the Mars Reconnaissance Orbiter.

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • On-demand Event Event Date: December 18, 2014 The Internet of Things (IoT) incorporates physical devices into business processes using predictive analytics. While it relies heavily on existing Internet technologies, it differs by including physical devices, specialized protocols, physical analytics, and a unique partner network. To capture the real business value of IoT, the industry must move beyond customized projects to general patterns and platforms. Check out this webcast and join industry experts as …

  • Today's agile organizations pose operations teams with a tremendous challenge: to deploy new releases to production immediately after development and testing is completed. To ensure that applications are deployed successfully, an automatic and transparent process is required. We refer to this process as Zero Touch Deployment™. This white paper reviews two approaches to Zero Touch Deployment--a script-based solution and a release automation platform. The article discusses how each can solve the key …

Most Popular Programming Stories

More for Developers

RSS Feeds