Files
FC1/Editor/Util/ImageUtil.cpp
romkazvo 34d6c5d489 123
2023-08-07 19:29:24 +08:00

875 lines
21 KiB
C++

////////////////////////////////////////////////////////////////////////////
//
// 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<char *> ((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<uchar> 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);
}