To display a huge image... how to do it?

I want to display a huge image (usually over 1G), I cannot load it to memory because of its huge size. The only way is to display a portion of the image, then pan it. Do you have any idea about how to to make it run well? Can I use "FileMapping"? Thanks a lot for your opinion, I appreciate it.
VCFanAsked:
Who is Participating?
 
DanRollinsConnect With a Mentor Commented:
This version processes the image data by seeking around in the file -- it never tries to load the entire file at once.

You can make this a lot faster in several ways:

1) No need to create a new DIBSection each time.  Just create it once and pass it into the (modified) GetImgSubset() fn.

2) Create a larger 'cache' DIB (say 2000x1000) then pan around in that (use BitBlit orStretchDiBits to copy part of it to the screen) until you get near the edge, then read the file for another chunk.

3) When the viewer has panned to the edge, there is no need to re-read the whole cache image.  For instance, when the user is near the right side of the 'cache' move the image data to the right and fill in the 'hole' with data from the file.  As the user keeps moving, new data is there for viewing.

4) Updating the cache DIB could be done in a worker thread.  You could keep track of how close the user is coming to the edge and be ready with a new cach DIB when it is needed (so the user does not need to wait).


//------------------------------------------------------
HBITMAP GetImgSubset( CFile& cfBmp, CRect& rc )
{
                      //  only needs to be sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFO)
     BYTE buf[1000];  //  but handy for eyeball check
     cfBmp.Seek(0, CFile::begin);
     cfBmp.Read(buf, 1000); // Read header info TBD: check error

     BITMAPFILEHEADER* pBMFH= (BITMAPFILEHEADER*)buf;
     BITMAPINFO*       pBMI=  (BITMAPINFO*)     (pBMFH+1);    

     DWORD nBitsFileOffset= pBMFH->bfOffBits;  // first RGB (x,y)=(0,0) is here

     int nSrcWide= pBMI->bmiHeader.biWidth;
     int nSrcHigh= pBMI->bmiHeader.biHeight;

     int nSrcBytesPerLine= ((nSrcWide * 3)+3) & 0xfffffffc;  // divible by 4

     int nDstWide= rc.Width();
     int nDstHigh= rc.Height();

     pBMI->bmiHeader.biHeight= -nDstHigh;  // fixes upside-down-ness
     pBMI->bmiHeader.biWidth=  nDstWide;

     //---------------------------------- create the destination bitmap
     void* pBits=    0;
     HWND    hWnd=   AfxGetMainWnd()->m_hWnd;
     HDC     hDC=    ::GetDC( hWnd );
     HBITMAP hbmRet= CreateDIBSection( hDC, pBMI,DIB_RGB_COLORS, &pBits, 0,0 );
     ::ReleaseDC( hWnd, hDC );

     if ( hbmRet == 0 ) {
          AfxMessageBox("CreateDIBSection failed!" );
     }
     //----------------------------
     DIBSECTION rDS;
     GetObject( hbmRet, sizeof(DIBSECTION), &rDS );
     
     int nDstBytesPerLine= rDS.dsBm.bmWidthBytes;

     BYTE* pDstBits= (BYTE*)pBits;
     for (int y= rc.top; y<rc.bottom; y++ ) {
          int j= (nSrcHigh-1) - y;   // fixes upside-down-ness
          int nFileOffset= nBitsFileOffset + (j*nSrcBytesPerLine);

          nFileOffset += rc.left * 3;

          cfBmp.Seek( nFileOffset, CFile::begin);
          cfBmp.Read( pDstBits, nDstBytesPerLine );
          pDstBits += nDstBytesPerLine;
     }
     return( hbmRet );
}


CRect   m_rc;
CFile   m_cfBmp;
HBITMAP m_hBmp;

///////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////
void CBigBmpDlg::OnButton1()
{
     m_cfBmp.Open( "C:\\temp\\large.bmp", CFile::modeRead );
     m_rc= CRect(0,0, 500,500 );
     m_hBmp= GetImgSubset( m_cfBmp, m_rc );
     m_ctlBmpShow.SetBitmap( m_hBmp );
}

void CBigBmpDlg::OnRight()
{
     m_rc.OffsetRect(10,0);

     ::DeleteObject(m_hBmp);
     m_hBmp= GetImgSubset( m_cfBmp, m_rc );
     m_ctlBmpShow.SetBitmap( m_hBmp );
}

void CBigBmpDlg::OnDown()
{
     m_rc.OffsetRect(0,10);

     ::DeleteObject(m_hBmp);
     m_hBmp= GetImgSubset( m_cfBmp, m_rc );
     m_ctlBmpShow.SetBitmap( m_hBmp );
}
0
 
DanRollinsCommented:
Yes, it appears that you can use FileMapping.  It  involves using CreateFileMapping and MapViewOfFile and there is a trick having to do with forcing the dibsection data to be aligned on a DWORD boundary.  

Alas, I can't find a pat answer for you.  Perhaps someone else can locate some sample code.  If you up the points, I'll take a whack at it.

-- Dan
0
 
VCFanAuthor Commented:
Sure, I am waiting for your further help. :)
0
Cloud Class® Course: Ruby Fundamentals

This course will introduce you to Ruby, as well as teach you about classes, methods, variables, data structures, loops, enumerable methods, and finishing touches.

 
DanRollinsCommented:
OK Here's a FIRST DRAFT -- it is for testing purposes ONLY:

THere is a big problem that I'm not certain how to attack:  The image area (the bits) do not normally end up on a DWORD-aligned boundary -- and that is a requirement for using the CreateDIBSection API which seems to be the logical way to attack this problem.

As a proof-of-concept, I just added an offset so that the bits are aligned (I had to make the images shorter to avoid a related error).  The result is that I can access the data, but it is color-shifted (RGB RGB RGB is interpetted as GBR GBR GBR).  

Note: I woked on a 2MB file (the largest I could find!) and the girl looks like one of those Intel 'bluemen' lol.

This code shows how to do the file mapping and so forth and actually displays a specified portion of a large bitmap.  I'd like you to try it and single-step through the code. and examine all variables ate each step.  Perhaps you can see something different.

Do you have access to the program that creates these whopping GB+ files?  Is there anything on on earth that can read them?  If you have access to the program, you could add a couple of filler bytes ate one place in the file and the work would be done.

Otherwise, I'll need to either shift the entire file forward by 2 bytes (probably not feasible) or do manual operations of copying subsets of the data into a temporary buffer.  That much coding will take me hours...

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

void GetLastErrorStr( CString& sDest );
HBITMAP GetImgSubSet( HBITMAP hbmSrc, RECT* prcSrc, BITMAPINFO* pBMI, void* pBits ) ;

void CD06Dlg::OnButton1()
{
      HANDLE            hFile;
      HANDLE            hFileMapping;
      BITMAPFILEHEADER* pBMFH;
      BITMAPINFO*       pBMI;    // bfOffBits is not divisible by 4.
      BITMAPINFOHEADER* pBMIH;

      hFile=        CreateFile( "C:\\temp\\large.bmp",GENERIC_READ|GENERIC_WRITE,0,0,OPEN_EXISTING,0,NULL);
      hFileMapping= CreateFileMapping( hFile,NULL,PAGE_READWRITE,0,0,0 );

      CString s;
      GetLastErrorStr( s );

      pBMFH= (BITMAPFILEHEADER*)MapViewOfFile( hFileMapping,FILE_MAP_ALL_ACCESS,0,0,0);  // entire file
      pBMI=  (BITMAPINFO*)     (pBMFH+1);

      GetLastErrorStr( s );

      DWORD dwOffset= pBMFH->bfOffBits;

      if ( (dwOffset & 3 ) != 0 ) {
            MessageBox("Problem mapping: bit data not dword-aligned!" );
            int nAdjust= dwOffset % 4;  // usually 2
            dwOffset += nAdjust;
            pBMI->bmiHeader.biHeight--; // discared last scanline for this test
      }

      HDC dc= CreateCompatibleDC(NULL);

      void* pBits=    0;

      HBITMAP hBmp= CreateDIBSection(dc,pBMI,DIB_RGB_COLORS, &pBits, hFileMapping, dwOffset );
      if (hBmp == 0 ) {
            MessageBox("CreateDIBSection failed!" );
      }
      GetLastErrorStr( s );  // useless

      pBMIH= (BITMAPINFOHEADER*)pBMI;
      int nWidth= pBMIH->biWidth;
      int nHigh=  pBMIH->biHeight;

      RECT rc= {100,100,400,400 };
      static HBITMAP hBmpNew= GetImgSubSet( hBmp, &rc, pBMI, pBits) ;

      m_ctlBmpShow.SetBitmap( hBmpNew );

      DeleteDC(dc);
      CloseHandle( hFile ); // or just exit the testing program
}

HBITMAP GetImgSubSet( HBITMAP hbmSrc, RECT* prcSrc, BITMAPINFO* zpBMI, void* pBits )
{
      HWND hWnd= AfxGetMainWnd()->m_hWnd;
      int nHigh= prcSrc->bottom - prcSrc->top;
      int nWide= prcSrc->right  - prcSrc->left;

      //---------------------------- create the destination bitmap
      HDC hDCMem= ::CreateCompatibleDC( NULL );
      HBITMAP hBmp= NULL;
      HDC hDC= ::GetDC( hWnd );
      hBmp= CreateCompatibleBitmap( hDC, nWide, nHigh );
      ::ReleaseDC( hWnd, hDC );

      HGDIOBJ hOldBmp= SelectObject( hDCMem, hBmp );

      BITMAP rBmp;
      GetObject( hbmSrc, sizeof(BITMAP), &rBmp );

      DIBSECTION rDS;
      GetObject( hbmSrc, sizeof(DIBSECTION), &rDS );


      BITMAPINFO* pBMI= (BITMAPINFO*)&rDS.dsBmih;
      
      int nScansCopied=
            StretchDIBits( hDCMem,
            0,0,                       // dest location
            nWide, nHigh,              // dest size
            prcSrc->left, prcSrc->top, // src Location
            nWide, nHigh,              // src size
            pBits,
            pBMI,
            DIB_RGB_COLORS,
            SRCCOPY
      );

      ::SelectObject( hDCMem, hOldBmp );
      ::DeleteObject( hDCMem );

      return( hBmp );
}
0
 
VCFanAuthor Commented:
Hello Dan, thanks a lot for your help. I tried your code, some of which is ingenious, but it failed at "MapViewOfFile" cuz I used a whopping image file (over 900M) which made it memory insufficient. How could I get it over?
0
 
DanRollinsCommented:
On second thought, this may not work.  I can't find a single reference about how to handle this.

How are most of these bitmaps laid out?  Very wide and not too tall?   Is there someplace I could download one for testing?

If the monster BMP file is certain to be a DIB with standard 24-bit color, it is possible to just open the file and read in carefully-selected chunks of it to form a DIBSection in RAM.  From that, I could extract a specified rectangle and return an HBITMAP of it.

If you bump the points again, I'll take a crack at that.

-- Dan
0
 
VCFanAuthor Commented:
Okay, I think it's tough while interesting, it's worth a study :)
0
 
VCFanAuthor Commented:
Dan, thank you for your hard working, I appreciate it. I think your answer meet my requirement basically, I will improve it by myself. Thanks. Chances are I will have more questions in the future, please give me your Email address in order to let you know that I have new questions about a display of big image. :) Thanks again!
0
 
DanRollinsCommented:
I worked many hours to help you for free.  I solved a problem that no other expert would touch.  A problem that is considered impossible to solve.  Then you give me a B!
 
>>please give me your Email address in order to let you know that I have new questions

This has made me laugh quite hard.  My side hurts.

-- Dan
0
 
VCFanAuthor Commented:
I am so sorry, Dan. I don't know much of this website and I must give you an "A" if I can choose again.

I will take my words back. Sorry
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

All Courses

From novice to tech pro — start learning today.