VC++ Example: Convert / Turn bitmap DIB File to a JPEG file
|
|
Wes Rogers.
This article
presents code which
allows you to take a DIB
and write it to a JPEG
file. Other bitmap
and palette example code
at this web site can show you how to turn a bitmap
into a DIB (DDBToDIB,
etc.).
The code has been
improved to handle all bitmap
formats. The earlier version did not handle 32-bit or
16-bit DIB's.
Acknowledgements:
This code depends on the jpeg.lib code written by the
Independent JPEG Group
(Thomas G. Lane and company).
The code
assumes that you have installed their jpeg.lib
library project.
To get jpeg.lib:
1) Go to Ulrich von
Zadow's "Class library
for image file
decoding" article, and click on the "Links"
link.
2) Click on the "LIBJPEG
ver 6b" link to download the "vanilla"
libjpeg.
3) Do not use Mr. von
Zadow's "Paintlib" version of libjpeg, because
he seems to have made some internal changes for his own
use.
The jpeg.lib
project (LibJpeg) is set up to compile under numerous
operating systems. The download includes a file called
install.doc. Read the sections relevant to VC++/Windows.
You have to rename a couple of files, etc.
The first time I
installed and compiled libjpeg, it took a long time and
was very aggravating. I then re-read the instructions, and
it took about fifteen minutes. I'm sure there's a lesson
here...
Testing Notes:
To test the code, I used
MS Paint to create bitmap files
in the four supported formats.
I then set up a VC++
project which read the bitmap
files, and used the functions below to turn
convert them into JPEG
files (.jpg).
I then used Julian
Smart's CImage demo application to display them.
I also used Internet
Explorer as well as MS Word to display them.
I also created a jpeg
file by loading an internal resource (see the example
below). The largest bitmap file
I dealt with was about 390 KB.
Error trapping:
libjpeg includes a number
of provisions for improving error detection, and reporting
them in a good manner. I have not investigated these, and
the code does not include any of them. Right now, an
internal error will simply cause the code to stop cold.
To get at jpeg.lib
properly in your project:
- Under
tools->options, directories tab:
- Set the Include
Files to include the path to your jpeg.lib
project
- Set the Library
Files to include the path to your jpeg.lib
release directory
- Under
Project->Settings, Link tab:
- Add jpeg.lib
to the Object/library modules list
Note on Quality
Settings:
For the IDB_TEST
resource, I tried two quality settings: 10 and 100. At
"10", the image
had a lot of black spottiness, and took up 1 KB. At
"100" the image looked very good, and took up 4
KB.
/////////////////////////////////////////////////////////////
//Example of use:
/////////////////////////////////////////////////////////////
void MakeJpeg()
{
CBitmap cBitmap;
BITMAP bm;
CString csMsg = "";
cBitmap.LoadBitmap(IDB_TEST);
cBitmap.GetBitmap(&bm);
HANDLE hDib = DDBToDIB((HBITMAP)cBitmap,
BI_RGB,
NULL); //Use default palette
//Turn DIB into JPEG file
if (hDib != NULL)
{
if (!JpegFromDib(hDib,
100, //Quality setting
"test.jpg",
&csMsg))
{
AfxMessageBox(csMsg);
}
else
AfxMessageBox("test.jpg created");
::GlobalFree(hDib);
}
else
AfxMessageBox("Failed to load IDB_TEST");
}
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
//Header file code
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
//In the relevant header file (up near the top):
#include "jpeglib.h"
BOOL JpegFromDib(HANDLE hDib, //Handle to DIB
int nQuality, //JPEG quality (0-100)
CString csJpeg, //Pathname to target jpeg file
CString* pcsMsg); //Error msg to return
BOOL BuildSamps(HANDLE hDib,
int nSampsPerRow,
struct jpeg_compress_struct cinfo,
JSAMPARRAY jsmpArray,
CString* pcsMsg);
RGBQUAD QuadFromWord(WORD b16);
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
// Source file code
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
extern "C"
{
#include "jpeglib.h"
}
#include
struct ima_error_mgr
{
struct jpeg_error_mgr pub; //"public" fields
jmp_buf setjmp_buffer; //for return to caller
};
////////////////////////////////////////////////////////////////////////////
//This function takes the contents of a DIB
//and turns it into a JPEG file.
//
//The DIB may be monochrome, 16-color, 256-color, or 24-bit color.
//
//Any functions or data items beginning with "jpeg_" belong to jpeg.lib,
//and are not included here.
//
//The function assumes 3 color components per pixel.
/////////////////////////////////////////////////////////////////////////////
BOOL JpegFromDib(HANDLE hDib, //Handle to DIB
int nQuality, //JPEG quality (0-100)
CString csJpeg, //Pathname to jpeg file
CString* pcsMsg) //Error msg to return
{
//Basic sanity checks...
if (nQuality <0 || nQuality> 100 ||
hDib == NULL ||
pcsMsg == NULL ||
csJpeg == "")
{
if (pcsMsg != NULL)
*pcsMsg = "Invalid input data";
return FALSE;
}
*pcsMsg = "";
LPBITMAPINFOHEADER lpbi = (LPBITMAPINFOHEADER)hDib;
byte *buf2 = 0;
//Use libjpeg functions to write scanlines to disk in JPEG format
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
FILE* pOutFile; //Target file
int nSampsPerRow; //Physical row width in image buffer
JSAMPARRAY jsmpArray; //Pixel RGB buffer for JPEG file
cinfo.err = jpeg_std_error(&jerr); //Use default error handling (ugly!)
jpeg_create_compress(&cinfo);
if ((pOutFile = fopen(csJpeg, "wb")) == NULL)
{
*pcsMsg = "Cannot open ";
*pcsMsg += csJpeg;
jpeg_destroy_compress(&cinfo);
return FALSE;
}
jpeg_stdio_dest(&cinfo, pOutFile);
cinfo.image_width = lpbi->biWidth; //Image width and height, in pixels
cinfo.image_height = lpbi->biHeight;
cinfo.input_components = 3; //Color components per pixel
//(RGB_PIXELSIZE - see jmorecfg.h)
cinfo.in_color_space = JCS_RGB; //Colorspace of input image
jpeg_set_defaults(&cinfo);
jpeg_set_quality(&cinfo,
nQuality, //Quality: 0-100 scale
TRUE); //Limit to baseline-JPEG values
jpeg_start_compress(&cinfo, TRUE);
//JSAMPLEs per row in output buffer
nSampsPerRow = cinfo.image_width * cinfo.input_components;
//Allocate array of pixel RGB values
jsmpArray = (*cinfo.mem->alloc_sarray)
((j_common_ptr) &cinfo,
JPOOL_IMAGE,
nSampsPerRow,
cinfo.image_height);
if (DibToSamps(hDib,
nSampsPerRow,
cinfo,
jsmpArray,
pcsMsg))
{
//Write the array of scan lines to the JPEG file
(void)jpeg_write_scanlines(&cinfo,
jsmpArray,
cinfo.image_height);
}
jpeg_finish_compress(&cinfo); //Always finish
fclose(pOutFile);
jpeg_destroy_compress(&cinfo); //Free resources
if (*pcsMsg != "")
return FALSE;
else
return TRUE;
}
////////////////////////////////////////////////////////////////
//This function fills a jsmpArray with the RGB values
//for the CBitmap.
//
//It has been improved to handle all legal bitmap formats.
//
//A jsmpArray is a big array of RGB values, 3 bytes per value.
//
//Note that rows of pixels are processed bottom to top:
//The data in the jsamp array must be arranged top to bottom.
////////////////////////////////////////////////////////////////
BOOL DibToSamps(HANDLE hDib,
int nSampsPerRow,
struct jpeg_compress_struct cinfo,
JSAMPARRAY jsmpPixels,
CString* pcsMsg)
{
//Sanity...
if (hDib == NULL ||
nSampsPerRow <= 0 || pcsMsg="=" NULL) { if (pcsMsg !="NULL)" *pcsMsg="Invalid input data" ; return FALSE; } int r="0," p="0," q="0," b="0," n="0," nUnused="0," nBytesWide="0," nUsed="0," nLastBits="0," nLastNibs="0," nCTEntries="0," nRow="0," nByte="0," nPixel="0;" BYTE bytCTEnt="0;" LPBITMAPINFOHEADER pbBmHdr="(LPBITMAPINFOHEADER)hDib;" //The bit count tells you the format of the bitmap: //Decide how many entries will be in the color table (if any) switch (pbBmHdr->biBitCount)
{
case 1:
nCTEntries = 2; //Monochrome
break;
case 4:
nCTEntries = 16; //16-color
break;
case 8:
nCTEntries = 256; //256-color
break;
case 16:
case 24:
case 32:
nCTEntries = 0; //No color table needed
break;
default:
*pcsMsg = "Invalid bitmap bit count";
return FALSE; //Unsupported format
}
//Point to the color table and pixels
DWORD dwCTab = (DWORD)pbBmHdr + pbBmHdr->biSize;
LPRGBQUAD pCTab = (LPRGBQUAD)(dwCTab);
LPSTR lpBits = (LPSTR)pbBmHdr +
(WORD)pbBmHdr->biSize +
(WORD)(nCTEntries * sizeof(RGBQUAD));
//Different formats for the image bits
LPBYTE lpPixels = (LPBYTE) lpBits;
RGBQUAD* pRgbQs = (RGBQUAD*)lpBits;
WORD* wPixels = (WORD*) lpBits;
//Set up the jsamps according to the bitmap's format.
//Note that rows are processed bottom to top, because
//that's how bitmaps are created.
switch (pbBmHdr->biBitCount)
{
case 1:
nUsed = (pbBmHdr->biWidth + 7) / 8;
nUnused = (((nUsed + 3) / 4) * 4) - nUsed;
nBytesWide = nUsed + nUnused;
nLastBits = 8 - ((nUsed * 8) - pbBmHdr->biWidth);
for (r=0; r
biHeight; r++)
{
for (p=0,q=0; p
biHeight-r-1) * nBytesWide;
nByte = nRow + p;
int nBUsed = (p <(nUsed 1)) ? 8 : nLastBits; for(b="0;" b < nBUsed;b++) { bytCTEnt="lpPixels[nByte]" << b; bytCTEnt="bytCTEnt">> 7;
jsmpPixels[r][q+0] = pCTab[bytCTEnt].rgbRed;
jsmpPixels[r][q+1] = pCTab[bytCTEnt].rgbGreen;
jsmpPixels[r][q+2] = pCTab[bytCTEnt].rgbBlue;
q += 3;
}
}
}
break;
case 4:
nUsed = (pbBmHdr->biWidth + 1) / 2;
nUnused = (((nUsed + 3) / 4) * 4) - nUsed;
nBytesWide = nUsed + nUnused;
nLastNibs = 2 - ((nUsed * 2) - pbBmHdr->biWidth);
for (r=0; r
biHeight;r++)
{
for (p=0,q=0; p
biHeight-r-1) * nBytesWide;
nByte = nRow + p;
int nNibbles = (p
> (4-(n*4));
jsmpPixels[r][q+0] = pCTab[bytCTEnt].rgbRed;
jsmpPixels[r][q+1] = pCTab[bytCTEnt].rgbGreen;
jsmpPixels[r][q+2] = pCTab[bytCTEnt].rgbBlue;
q += 3;
}
}
}
break;
default:
case 8: //Each byte is a pointer to a pixel color
nUnused = (((pbBmHdr->biWidth + 3) / 4) * 4) -
pbBmHdr->biWidth;
for (r=0;r
biHeight; r++)
{
for (p=0,q=0; p
biWidth; p++,q+=3)
{
nRow = (pbBmHdr->biHeight-r-1) * (pbBmHdr->biWidth + nUnused);
nPixel = nRow + p;
jsmpPixels[r][q+0] = pCTab[lpPixels[nPixel]].rgbRed;
jsmpPixels[r][q+1] = pCTab[lpPixels[nPixel]].rgbGreen;
jsmpPixels[r][q+2] = pCTab[lpPixels[nPixel]].rgbBlue;
}
}
break;
case 16: //Hi-color (16 bits per pixel)
for (r=0;r
biHeight; r++)
{
for (p=0,q=0; p
biWidth; p++,q+=3)
{
nRow = (pbBmHdr->biHeight-r-1) * pbBmHdr->biWidth;
nPixel = nRow + p;
RGBQUAD quad = QuadFromWord(wPixels[nPixel]);
jsmpPixels[r][q+0] = quad.rgbRed;
jsmpPixels[r][q+1] = quad.rgbGreen;
jsmpPixels[r][q+2] = quad.rgbBlue;
}
}
break;
case 24:
nBytesWide = (pbBmHdr->biWidth*3);
nUnused = (((nBytesWide + 3) / 4) * 4) -
nBytesWide;
nBytesWide += nUnused;
for (r=0;r
biHeight;r++)
{
for (p=0,q=0;p <(nBytesWide-nUnused); p+="3,q+=3)" { nRow="(pbBmHdr-">biHeight-r-1) * nBytesWide;
nPixel = nRow + p;
jsmpPixels[r][q+0] = lpPixels[nPixel+2]; //Red
jsmpPixels[r][q+1] = lpPixels[nPixel+1]; //Green
jsmpPixels[r][q+2] = lpPixels[nPixel+0]; //Blue
}
}
break;
case 32:
for (r=0; r
biHeight; r++)
{
for (p=0,q=0; p
biWidth; p++,q+=3)
{
nRow = (pbBmHdr->biHeight-r-1) *
pbBmHdr->biWidth;
nPixel = nRow + p;
jsmpPixels[r][q+0] = pRgbQs[nPixel].rgbRed;
jsmpPixels[r][q+1] = pRgbQs[nPixel].rgbGreen;
jsmpPixels[r][q+2] = pRgbQs[nPixel].rgbBlue;
}
}
break;
} //end switch
return TRUE;
}
////////////////////////////////////////
//This function turns a 16-bit pixel
//into an RGBQUAD value.
////////////////////////////////////////
RGBQUAD QuadFromWord(WORD b16)
{
BYTE bytVals[] =
{
0, 16, 24, 32, 40, 48, 56, 64,
72, 80, 88, 96, 104,112,120,128,
136,144,152,160,168,176,184,192,
200,208,216,224,232,240,248,255
};
WORD wR = b16;
WORD wG = b16;
WORD wB = b16;
wR <<= 1; wR >>= 11;
wG <<= 6; wG >>= 11;
wB <<= 11; wB >>= 11;
RGBQUAD rgb;
rgb.rgbReserved = 0;
rgb.rgbBlue = bytVals[wB];
rgb.rgbGreen = bytVals[wG];
rgb.rgbRed = bytVals[wR];
return rgb;
}
|