Quick Image Stretching Technique

.

Environment: C6, Win32

Overview

The ability to draw an image on the screen that is a different size than the original source image is incredibly easy with Win32's GDI StretchBlt function and its variants. However, using StretchBlt with large images, especially with a large amount of stretch, is very slow. Also, shrinking images with StretchBlt often produces poor image quality. This article provides some simple techniques to get around the performance and image quality limitations of StretchBlt.

The most aggravating effect caused by the poor performance of StretchBlt is to make scrolling very slow. If you call StretchBlt to draw an image each time the scroll bars are moved, you may be in for a long wait. A simple solution is to only call StretchBlt when the size of the image actually needs to change. Instead of stretching each time the image is drawn to the screen, only stretch it when necessary and rely on BitBlt (which is much faster) for the majority of your drawing. The downside of this approach means storing a copy of the stretched image in memory as well as the original source image. Considering the ever-present need for speed and these times of cheap memory modules, making a copy seems very reasonable.

Image quality (for shrinking) can be improved over using StretchBlt by a simple image sampling function. The DIBSection::Resize method samples the source image to produce an image of a different size. Unset the ALTERNATE_RESIZE_METHOD define to see the difference between the sampling method and StretchBlt.

An Outline of the Approach

  1. Load an image from a disk file and store the contents in a DIBSection object.
  2. When the image size changes, stretch the source image into another DIBSection object.
  3. As the image is scrolled or re-painted, simply BitBlt the second DIBSection object.

A Code Sample that Implements the Approach

void QSView::StretchImage(double scale_factor)
{
  QSDoc * doc = GetDocument();
  if (doc)
  {
    DIBSection * dib = doc->GetDIB();

    if (dib && dib->IsCreated())
    {
      if (m_view_dib.IsCreated())
      {
        m_scale_factor = scale_factor;

        if (m_scale_factor < 0.1) m_scale_factor = 0.1;
        if (m_scale_factor > 2.0) m_scale_factor = 2.0;

        unsigned long w = (int)(dib->Width()
                        * scale_factor + 0.50);
        unsigned long h = (int)(dib->Height()
                        * scale_factor + 0.50);

        m_size.cx = w;
        m_size.cy = h;

#ifdef PRE_STRETCH_METHOD
        if ((w != m_view_dib.Width()) ||
            (h != m_view_dib.Height()))
        {
          m_view_dib.Close();

          m_view_dib.Create(w,h,dib->GetBitCount());

          if (m_view_dib.IsCreated())
          {
            StretchBlt((HDC)*m_view_dib.GetDC(),0,0,w,h,(HDC)
                            *dib->GetDC(),0,0,dib->Width(),
                             dib->Height(),SRCCOPY);
          }
        }
#endif

        SetScrollSizes(MM_TEXT,m_size);

        // white out the screen before drawing image
        CClientDC dc(this);
        CRect r;
        GetClientRect(&r);
        dc.PatBlt(0,0,r.Width()-1,r.Height()-1,WHITENESS);

        ShowImage();
      }
    }
  }
}

void QSView::ShowImage(void)
{
  if (m_view_dib.IsCreated())
  {
    CClientDC dc(this);

    CPoint sp = GetScrollPosition();

#ifdef PRE_STRETCH_METHOD
    m_view_dib.Draw(&dc,sp.x,sp.y);      // calls BitBlt
#else
    StretchBlt((HDC)dc,0,0,m_size.cx,m_size.cy,
               (HDC)*m_view_dib.GetDC(),
      (int)(0.50 + sp.x/m_scale_factor),(int)
           (0.5 + sp.y/m_scale_factor),
      m_view_dib.Width(),m_view_dib.Height(),SRCCOPY);
#endif
  }
}

Some Information About the Sample Project

This article is NOT intended to be the final word on image stretching for Windows; it is merely one technique to make your apps respond a little better.

The sample project has several DEFINEs.

The PRE_STRETCH_METHOD define can be turned off if you want to see the difference between stretching each time and storing the stretched image.

The ALTERNATE_RESIZE_METHOD define enables a sampling routine for re-sizing the image, instead of StretchBlt.

The NO_FLICKER define uses a client-window-sized DIB to draw the image to the screen. This reduces the flicker associated with making the non-image areas white.

All three settings are defined by default within the sample project.

A test image file was included with the project; however, because of the small size of the image it is difficult to see the speed difference between the two methods. The project only supports reading 24-bit BMP files.

Please do NOT post comments about the project's inability to read other file formats; this article is not about image file formats.

The sample project was built using VC++6 on NT4 SP6 using a 32-bit color setting.

Enjoy.

Downloads

Download demo project - 212 Kb



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.