//////////////////////////////////////////////////////////////////////////// // // Crytek Engine Source File. // Copyright (C), Crytek Studios, 2001. // ------------------------------------------------------------------------- // File name: ImageUtil.cpp // Version: v1.00 // Created: 30/1/2002 by Timur. // Compilers: Visual C++ 6.0 // Description: Image utilities implementation. // ------------------------------------------------------------------------- // History: // //////////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "ImageUtil.h" #include "ImageGif.h" #include "Image_DXTC.h" #include "CryFile.h" #ifndef WIN64 // Required linking with (Intels Jpeg Library) IJL15.LIB #include "ijl.h" #endif ////////////////////////////////////////////////////////////////////////// bool CImageUtil::SaveBitmap( const CString &szFileName, CImage &inImage,bool inverseY ) { //////////////////////////////////////////////////////////////////////// // Simple DIB save code //////////////////////////////////////////////////////////////////////// HANDLE hfile; DWORD dwBytes; unsigned int i; DWORD *pLine1 = NULL; DWORD *pLine2 = NULL; DWORD *pTemp = NULL; BITMAPFILEHEADER bitmapfileheader; BITMAPINFOHEADER bitmapinfoheader; CLogFile::FormatLine("Saving data to bitmap... %s", (const char*)szFileName); int dwWidth = inImage.GetWidth(); int dwHeight = inImage.GetHeight(); DWORD *pData = (DWORD*)inImage.GetData(); uchar *pImage = new uchar[dwWidth*dwHeight*3]; i = 0; for (int y = 0; y < dwHeight; y++) { int src_y=y; if(inverseY) src_y=(dwHeight-1)-y; for (int x = 0; x < dwWidth; x++) { DWORD c = pData[x+src_y*dwWidth]; pImage[i] = GetBValue(c); pImage[i+1] = GetGValue(c); pImage[i+2] = GetRValue(c); i+=3; } } // Fill in bitmap structures bitmapfileheader.bfType = 0x4D42; bitmapfileheader.bfSize = (dwWidth * dwHeight * 3) + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); bitmapfileheader.bfReserved1 = 0; bitmapfileheader.bfReserved2 = 0; bitmapfileheader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + (0 * sizeof(RGBQUAD)); bitmapinfoheader.biSize = sizeof(BITMAPINFOHEADER); bitmapinfoheader.biWidth = dwWidth; bitmapinfoheader.biHeight = dwHeight; bitmapinfoheader.biPlanes = 1; bitmapinfoheader.biBitCount = (WORD) 24; bitmapinfoheader.biCompression = BI_RGB; bitmapinfoheader.biSizeImage = 0; bitmapinfoheader.biXPelsPerMeter = 0; bitmapinfoheader.biYPelsPerMeter = 0; bitmapinfoheader.biClrUsed = 0; bitmapinfoheader.biClrImportant = 0; // Write bitmap to disk hfile = CreateFile(szFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hfile == INVALID_HANDLE_VALUE) { delete []pImage; return false; } // Write the headers to the file WriteFile(hfile, &bitmapfileheader, sizeof(BITMAPFILEHEADER), &dwBytes, NULL); WriteFile(hfile, &bitmapinfoheader, sizeof(BITMAPINFOHEADER), &dwBytes, NULL); // Write the data DWORD written; WriteFile(hfile, pImage, (dwWidth * dwHeight * 3), &written, NULL); CloseHandle(hfile); delete []pImage; // Success return true; } ////////////////////////////////////////////////////////////////////////// bool CImageUtil::SaveJPEG( const CString &strFileName,CImage &inImage ) { #ifdef WIN64 return false; #else //WIN64 //////////////////////////////////////////////////////////////////////// // Save an array as a JPEG //////////////////////////////////////////////////////////////////////// bool bSuccess = true; JPEG_CORE_PROPERTIES Image; unsigned char *pImageData = NULL; unsigned char *pImageDataStart = NULL; CLogFile::FormatLine("Saving data to JPEG... %s", (const char*)strFileName); int dwWidth = inImage.GetWidth(); int dwHeight = inImage.GetHeight(); DWORD *pData = (DWORD*)inImage.GetData(); // Convert from RGBA to RGB // Allocate memory for the converted image pImageData = new unsigned char[dwWidth * dwHeight * 3]; // Set the loop pointer pImageDataStart = pImageData; DWORD *pDataEnd = &pData[dwWidth * dwHeight]; // Convert while (pData != pDataEnd) { // Extract the color channels and copy them into the indivdual // bytes of the converted image *pImageData++ = GetRValue(*pData); *pImageData++ = GetGValue(*pData); *pImageData++ = GetBValue(*pData); // Advance to the next source pixel pData++; } // Restore the pointer pImageData = pImageDataStart; // Init the JPEG structure memset(&Image, 0, sizeof(JPEG_CORE_PROPERTIES)); bool failed = false; // Initialize if (ijlInit(&Image) != IJL_OK) { Warning("Can't initialize Intel(R) JPEG library !"); return failed; } // Setup DIB Image.DIBWidth = dwWidth; Image.DIBHeight = dwHeight; Image.DIBBytes = pImageData; Image.DIBPadBytes = IJL_DIB_PAD_BYTES(Image.DIBWidth, 3); // Setup JPEG Image.JPGFile = const_cast ((const char*)strFileName); Image.JPGWidth = dwWidth; Image.JPGHeight = dwHeight; Image.jquality = 100; Image.DIBColor = IJL_RGB; // Remove the read-only attribute so the JPEG library can overwrite the file. SetFileAttributes(strFileName, FILE_ATTRIBUTE_NORMAL); // Write the image if (ijlWrite( &Image, IJL_JFILE_WRITEWHOLEIMAGE ) != IJL_OK) { Warning("Can't JPEG write image !"); bSuccess = false; } if (ijlFree(&Image) != IJL_OK) { Warning("Can't free Intel(R) JPEG library !"); } // Free the temporary memory delete [] pImageData; pImageData = 0; return bSuccess; #endif //WIN64 } ////////////////////////////////////////////////////////////////////////// bool CImageUtil::LoadJPEG( const CString &strFileName,CImage &outImage ) { #ifdef WIN64 return false; #else //WIN64 //////////////////////////////////////////////////////////////////////// // Loads a JPEG file and stores image dimension data in the passed // pointers. Memory for the image itself is allocated and passed // and the passed pointer is set // // TODO: Verify that this functions works //////////////////////////////////////////////////////////////////////// JPEG_CORE_PROPERTIES Image; ASSERT(!strFileName.IsEmpty()); BYTE *pImageData = NULL; CLogFile::FormatLine("Loading JPEG %s...", (const char*)strFileName ); ZeroMemory(&Image, sizeof(JPEG_CORE_PROPERTIES)); // Initialize the JPEG library if (ijlInit(&Image) != IJL_OK) { Warning("Can't initialize Intel(R) JPEG library !"); return false; } // Set the filename Image.JPGFile = strFileName; // Read the JPEG header if (ijlRead(&Image, IJL_JFILE_READPARAMS) != IJL_OK) { Warning("Error while reading JPEG file (Header) !"); return false; } if (Image.JPGChannels != 3) { CLogFile::FormatLine( "JPG File %s is not 3 channels jpeg format.",(const char*)strFileName ); return false; } // Allocate memory for the image pImageData = new BYTE[Image.JPGWidth * Image.JPGHeight * Image.JPGChannels]; ASSERT(pImageData); // Fill in the DIB header Image.DIBBytes = pImageData; Image.DIBColor = (Image.JPGChannels == 3) ? IJL_RGB : IJL_G; Image.DIBWidth = Image.JPGWidth; Image.DIBHeight = Image.JPGHeight; Image.DIBChannels = Image.JPGChannels; // Read the JPEG image if (ijlRead(&Image, IJL_JFILE_READWHOLEIMAGE) != IJL_OK) { Warning("Error while reading JPEG file (Whole Image) !"); ijlFree(&Image); delete []pImageData; return false; } // Free the image if (ijlFree(&Image) != IJL_OK) { Warning("Can't free Intel(R) JPEG library !"); delete []pImageData; return false; } int memSize = Image.JPGWidth*Image.JPGHeight*4; outImage.Allocate( Image.JPGWidth,Image.JPGHeight ); uint *trg = outImage.GetData(); if (!trg) { Warning( "CImageUtil::LoadJPEG Memory allocation faild\r\nFailed to allocate %dMb of RAM for Jpg %s", memSize/(1024*1024),(const char*)strFileName ); return false; } unsigned char *src = pImageData; if (src && trg) { for (int i = 0; i < Image.JPGHeight*Image.JPGWidth; i++) { *trg++ = RGB(src[0],src[1],src[2]) | 0xFF000000; src += 3; } } delete []pImageData; return true; #endif //WIN64 } ////////////////////////////////////////////////////////////////////////// bool CImageUtil::SavePGM( const CString &fileName, uint dwWidth, uint dwHeight, uint *pData) { FILE *file = fopen( fileName,"wt" ); if (!file) return false; char s[65536]; fprintf( file, "P2\n", s ); fprintf( file, "%d %d\n", dwWidth,dwHeight ); fprintf( file, "65535\n" ); for (int y = 0; y < dwHeight; y++) { for (int x = 0; x < dwWidth; x++) { fprintf( file, "%d ",(uint)pData[x+y*dwWidth] ); } fprintf( file, "\n" ); } fclose( file ); return true; } ////////////////////////////////////////////////////////////////////////// bool CImageUtil::LoadPGM( const CString &fileName, uint *pWidthOut, uint *pHeightOut, uint **pImageDataOut) { FILE *file = fopen( fileName,"rt" ); if (!file) return false; const char seps[] = " \n\t"; char *token; int width=0; int height=0; int numColors = 1; fseek( file,0,SEEK_END ); int fileSize = ftell(file); fseek( file,0,SEEK_SET ); char *str = new char[fileSize]; fread( str,fileSize,1,file ); token = strtok( str,seps ); while (token != NULL && token[0] == '#') { if (token != NULL && token[0] == '#') strtok( NULL, "\n" ); token = strtok( NULL, seps ); } if (stricmp(token,"P2") != 0) { // Bad file. not supported pgm. delete []str; fclose( file ); return false; } do { token = strtok( NULL, seps ); if (token != NULL && token[0] == '#') { strtok( NULL, "\n" ); } } while (token != NULL && token[0] == '#'); width = atoi(token); do { token = strtok( NULL, seps ); if (token != NULL && token[0] == '#') strtok( NULL, "\n" ); } while (token != NULL && token[0] == '#'); height = atoi(token); do { token = strtok( NULL, seps ); if (token != NULL && token[0] == '#') strtok( NULL, "\n" ); } while (token != NULL && token[0] == '#'); numColors = atoi(token); *pWidthOut = width; *pHeightOut = height; uint *pImage = new uint[width*height]; *pImageDataOut = pImage; uint *p = pImage; int size = width*height; int i = 0; while (token != NULL && i < size) { do { token = strtok( NULL,seps ); } while (token != NULL && token[0] == '#'); *p++ = atoi(token); i++; } delete []str; fclose( file ); return true; } ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// static inline ushort us_endian (const byte* ptr) { short n; memcpy(&n, ptr, sizeof(n)); return n; } static inline unsigned long ul_endian (const byte* ptr) { long n; memcpy(&n, ptr, sizeof(n)); return n; } static inline long l_endian (const byte* ptr) { long n; memcpy(&n, ptr, sizeof(n)); return n; } #define BFTYPE(x) us_endian((x) + 0) #define BFSIZE(x) ul_endian((x) + 2) #define BFOFFBITS(x) ul_endian((x) + 10) #define BISIZE(x) ul_endian((x) + 14) #define BIWIDTH(x) l_endian ((x) + 18) #define BIHEIGHT(x) l_endian ((x) + 22) #define BITCOUNT(x) us_endian((x) + 28) #define BICOMP(x) ul_endian((x) + 30) #define IMAGESIZE(x) ul_endian((x) + 34) #define BICLRUSED(x) ul_endian((x) + 46) #define BICLRIMP(x) ul_endian((x) + 50) #define BIPALETTE(x) ((x) + 54) // Type ID #define BM "BM" // Windows 3.1x, 95, NT, ... #define BA "BA" // OS/2 Bitmap Array #define CI "CI" // OS/2 Color Icon #define CP "CP" // OS/2 Color Pointer #define IC "IC" // OS/2 Icon #define PT "PT" // OS/2 Pointer // Possible values for the header size #define WinHSize 0x28 #define OS21xHSize 0x0C #define OS22xHSize 0xF0 // Possible values for the BPP setting #define Mono 1 // Monochrome bitmap #define _16Color 4 // 16 color bitmap #define _256Color 8 // 256 color bitmap #define HIGHCOLOR 16 // 16bit (high color) bitmap #define TRUECOLOR24 24 // 24bit (true color) bitmap #define TRUECOLOR32 32 // 32bit (true color) bitmap // Compression Types #ifndef BI_RGB #define BI_RGB 0 // none #define BI_RLE8 1 // RLE 8-bit / pixel #define BI_RLE4 2 // RLE 4-bit / pixel #define BI_BITFIELDS 3 // Bitfields #endif //=========================================================================== bool CImageUtil::LoadBmp( const CString &fileName,CImage &image ) { #pragma pack(push,1) struct SRGBcolor { uchar red, green, blue; }; struct SRGBPixel { uchar red, green, blue, alpha; }; #pragma pack(pop) std::vector data; CCryFile file; if (!file.Open( fileName,"rb")) { CLogFile::FormatLine( "File not found %s",(const char*)fileName ); return false; } long iSize = file.GetLength(); data.resize(iSize); uchar* iBuffer = &data[0]; file.Read( iBuffer,iSize ); if (!((memcmp(iBuffer, BM, 2) == 0) && BISIZE(iBuffer) == WinHSize)) { // Not bmp file. CLogFile::FormatLine( "Invalid BMP file format %s",(const char*)fileName ); return false; } int mWidth = BIWIDTH(iBuffer); int mHeight = BIHEIGHT(iBuffer); image.Allocate(mWidth,mHeight); const int bmp_size = mWidth * mHeight; byte *iPtr = iBuffer + BFOFFBITS(iBuffer); // The last scanline in BMP corresponds to the top line in the image int buffer_y = mWidth * (mHeight - 1); bool blip = false; if (BITCOUNT(iBuffer) == _256Color) { //mpIndexImage = mfGet_IndexImage(); byte *buffer = new uchar[mWidth*mHeight]; SRGBcolor mspPal[256]; SRGBcolor *pwork = mspPal; byte *inpal = BIPALETTE(iBuffer); //mfSet_bps (8); for (int color=0; color<256; color++, pwork++) { // Whacky BMP palette is in BGR order. pwork->blue = *inpal++; pwork->green = *inpal++; pwork->red = *inpal++; inpal++; // Skip unused byte. } if (BICOMP(iBuffer) == BI_RGB) { // Read the pixels from "top" to "bottom" while (iPtr < iBuffer + iSize && buffer_y >= 0) { memcpy (buffer + buffer_y, iPtr, mWidth); iPtr += mWidth; buffer_y -= mWidth; } /* endwhile */ } else if (BICOMP(iBuffer) == BI_RLE8) { // Decompress pixel data byte rl, rl1, i; // runlength byte clridx, clridx1; // colorindex int buffer_x = 0; while (iPtr < iBuffer + iSize && buffer_y >= 0) { rl = rl1 = *iPtr++; clridx = clridx1 = *iPtr++; if (rl == 0) if (clridx == 0) { // new scanline if (!blip) { // if we didnt already jumped to the new line, do it now buffer_x = 0; buffer_y -= mWidth; } continue; } else if (clridx == 1) // end of bitmap break; else if (clridx == 2) { // next 2 bytes mean column- and scanline- offset buffer_x += *iPtr++; buffer_y -= (mWidth * (*iPtr++)); continue; } else if (clridx > 2) rl1 = clridx; for ( i = 0; i < rl1; i++ ) { if (!rl) clridx1 = *iPtr++; buffer [buffer_y + buffer_x] = clridx1; if (++buffer_x >= mWidth) { buffer_x = 0; buffer_y -= mWidth; blip = true; } else blip = false; } // pad in case rl == 0 and clridx in [3..255] if (rl == 0 && (clridx & 0x01)) iPtr++; } } // Convert indexed to RGBA for (int y = 0; y < mHeight; y++) { for (int x = 0; x < mWidth; x++) { SRGBcolor &entry = mspPal[buffer[x+y*mWidth]]; image.ValueAt(x,y) = 0xFF000000 | RGB(entry.red,entry.green,entry.blue); } } delete buffer; return true; } else if (!BICLRUSED(iBuffer) && BITCOUNT(iBuffer) == TRUECOLOR24) { //mfSet_bps (24); SRGBPixel *buffer = (SRGBPixel*)image.GetData(); while (iPtr < iBuffer + iSize && buffer_y >= 0) { SRGBPixel *d = buffer + buffer_y; for (int x = mWidth; x; x--) { d->blue = *iPtr++; d->green = *iPtr++; d->red = *iPtr++; d->alpha = 255; d++; } /* endfor */ buffer_y -= mWidth; } return true; } else if (!BICLRUSED(iBuffer) && BITCOUNT(iBuffer) == TRUECOLOR32) { //mfSet_bps (32); SRGBPixel *buffer = (SRGBPixel*)image.GetData(); while (iPtr < iBuffer + iSize && buffer_y >= 0) { SRGBPixel *d = buffer + buffer_y; for (int x = mWidth; x; x--) { d->blue = *iPtr++; d->green = *iPtr++; d->red = *iPtr++; d->alpha = *iPtr++; d++; } /* endfor */ buffer_y -= mWidth; } return true; } CLogFile::FormatLine( "Unknown BMP image format %s",(const char*)fileName ); return false; } ////////////////////////////////////////////////////////////////////////// bool CImageUtil::LoadImage( const CString &fileName, CImage &image ) { char drive[_MAX_DRIVE]; char dir[_MAX_DIR]; char fname[_MAX_FNAME]; char ext[_MAX_EXT]; _splitpath( fileName,drive,dir,fname,ext ); if (stricmp(ext,".bmp") == 0) { return LoadBmp( fileName,image ); } else if (stricmp(ext,".jpg") == 0) { return LoadJPEG( fileName,image ); } else if (stricmp(ext,".gif") == 0) { CImageGif gif; return gif.Load( fileName,image ); } else if (stricmp(ext,".pgm") == 0) { UINT w,h; uint *pData; bool res = LoadPGM( fileName,&w,&h,&pData ); if (!res) return false; image.Allocate(w,h); memcpy( image.GetData(),pData,image.GetSize() ); delete []pData; } else if (stricmp(ext,".dds") == 0) { // Load DDS file. CImage_DXTC dds; return dds.Load( fileName,image ); } return false; } ////////////////////////////////////////////////////////////////////////// bool CImageUtil::SaveImage( const CString &fileName, CImage &image ) { char drive[_MAX_DRIVE]; char dir[_MAX_DIR]; char fname[_MAX_FNAME]; char ext[_MAX_EXT]; // Remove the read-only attribute so the file can be overwritten. SetFileAttributes(fileName,FILE_ATTRIBUTE_NORMAL); _splitpath( fileName,drive,dir,fname,ext ); if (stricmp(ext,".bmp") == 0) { return SaveBitmap( fileName,image ); } else if (stricmp(ext,".jpg") == 0) { return SaveJPEG( fileName,image ); } else if (stricmp(ext,".pgm") == 0) { return SavePGM( fileName,image.GetWidth(),image.GetHeight(),image.GetData() ); } return false; } ////////////////////////////////////////////////////////////////////////// void CImageUtil::ScaleToFit( const CByteImage &srcImage,CByteImage &trgImage ) { uint x,y,u,v; unsigned char *destRow,*dest,*src,*sourceRow; uint srcW = srcImage.GetWidth(); uint srcH = srcImage.GetHeight(); uint trgW = trgImage.GetWidth(); uint trgH = trgImage.GetHeight(); uint xratio = (srcW << 16)/trgW; uint yratio = (srcH << 16)/trgH; src = srcImage.GetData(); destRow = trgImage.GetData(); v = 0; for (y = 0; y < trgH; y++) { u=0; sourceRow = src + (v >> 16) * srcW; dest = destRow; for (x = 0; x < trgW; x++) { *dest++ = sourceRow[u>>16]; u += xratio; } v += yratio; destRow += trgW; } } ////////////////////////////////////////////////////////////////////////// void CImageUtil::ScaleToFit( const CImage &srcImage,CImage &trgImage ) { uint x,y,u,v; unsigned int *destRow,*dest,*src,*sourceRow; uint srcW = srcImage.GetWidth(); uint srcH = srcImage.GetHeight(); uint trgW = trgImage.GetWidth(); uint trgH = trgImage.GetHeight(); uint xratio = (srcW << 16)/trgW; uint yratio = (srcH << 16)/trgH; src = srcImage.GetData(); destRow = trgImage.GetData(); v = 0; for (y = 0; y < trgH; y++) { u=0; sourceRow = src + (v >> 16) * srcW; dest = destRow; for (x = 0; x < trgW; x++) { *dest++ = sourceRow[u>>16]; u += xratio; } v += yratio; destRow += trgW; } } ////////////////////////////////////////////////////////////////////////// void CImageUtil::SmoothImage( CByteImage &image,int numSteps ) { assert( numSteps > 0 ); uchar *buf = image.GetData(); int w = image.GetWidth(); int h = image.GetHeight(); for (int steps = 0; steps < numSteps; steps++) { // Smooth the image. for (int y = 1; y < h-1; y++) { // Precalculate for better speed uchar *ptr = &buf[y*w + 1]; for (int x = 1; x < w-1; x++) { // Smooth it out *ptr = ( (uint)ptr[1] + ptr[w] + ptr[-1] + ptr[-w] + ptr[w+1] + ptr[w-1] + ptr[-w+1] + ptr[-w-1] ) >> 3; // Next pixel ptr++; } } } } unsigned char CImageUtil::GetBilinearFilteredAt( const int iniX256, const int iniY256, const CByteImage &image ) { // assert(image.IsValid()); if(!image.IsValid())return(0); // this shouldn't be DWORD x=(DWORD)(iniX256) >> 8; DWORD y=(DWORD)(iniY256) >> 8; if(x>=image.GetWidth()-1 || y>=image.GetHeight()-1) return image.ValueAt(x,y); // border is not filtered, 255 to get in range 0..1 DWORD rx=(DWORD)(iniX256) & 0xff; // fractional aprt DWORD ry=(DWORD)(iniY256) & 0xff; // fractional aprt DWORD top=(DWORD)image.ValueAt((int)x ,(int)y )*(256-rx) // left top +(DWORD)image.ValueAt((int)x+1,(int)y )*rx; // right top DWORD bottom =(DWORD)image.ValueAt((int)x ,(int)y+1)*(256-rx) // left bottom +(DWORD)image.ValueAt((int)x+1,(int)y+1)*rx; // right bottom return (unsigned char)((top*(256-ry) + bottom*ry)>>16); }