Bitmap::Bitmap()
{
_Data = 0;
_Width = 0;
_Height = 0;
}
Bitmap::Bitmap(UINT Width, UINT Height)
{
_Data = 0;
Allocate(Width, Height);
}
Bitmap::~Bitmap()
{
FreeMemory();
}
Bitmap::Bitmap(const Bitmap &B)
{
_Data = NULL;
Allocate(B._Width,B._Height);
memcpy(_Data,B._Data,sizeof(RGBColor)*_Width*_Height);
}
Bitmap::Bitmap(const Bitmap &B, UINT x, UINT y, UINT Width, UINT Height)
{
_Data = NULL;
Allocate(Width, Height);
Clear();
for(UINT CurY = 0; CurY < _Height; CurY++)
{
for(UINT CurX = 0; CurX < _Width; CurX++)
{
_Data[CurY * _Width + CurX] = B[CurY + y][CurX + x];
}
}
}
Bitmap& Bitmap::operator = (const Bitmap &B)
{
Allocate(B._Width,B._Height);
memcpy(_Data,B._Data,sizeof(RGBColor)*_Width*_Height);
return *this;
}
void Bitmap::FreeMemory()
{
if(_Data)
{
delete[] _Data;
}
_Data = NULL;
_Width = 0;
_Height = 0;
}
void Bitmap::Allocate(UINT Width, UINT Height)
{
if(_Data == NULL || _Width != Width || _Height != Height)
{
FreeMemory();
_Width = Width;
_Height = Height;
_Data = new RGBColor[Width * Height];
}
}
void Bitmap::SaveBMP(const String &Filename) const
{
BITMAPFILEHEADER bmfh; BITMAPINFOHEADER bmih; FILE *file;
((unsigned char *)&bmfh.bfType)[0] = 'B';
((unsigned char *)&bmfh.bfType)[1] = 'M';
bmfh.bfSize = 54 + _Height * _Width * 4;
bmfh.bfReserved1 = 0;
bmfh.bfReserved2 = 0;
bmfh.bfOffBits = 54;
bmih.biSize = 40;
bmih.biWidth = _Width;
bmih.biHeight = _Height;
bmih.biPlanes = 1;
bmih.biBitCount = 32;
bmih.biCompression = 0;
bmih.biSizeImage = 0;
bmih.biXPelsPerMeter = 3800;
bmih.biYPelsPerMeter = 3800;
bmih.biClrUsed = 0;
bmih.biClrImportant = 0;
file = Utility::CheckedFOpen(Filename.CString(), "wb");
fwrite(&bmfh, sizeof(BITMAPFILEHEADER), 1, file);
fwrite(&bmih, sizeof(BITMAPINFOHEADER), 1, file);
fwrite(_Data, sizeof(RGBColor), _Width * _Height, file);
fclose(file);
}
void Bitmap::LoadBMP(const String &Filename)
{
BITMAPFILEHEADER bmfh; BITMAPINFOHEADER bmih; FILE *file;
file = Utility::CheckedFOpen(Filename.CString(), "rb");
fread(&bmfh, sizeof(BITMAPFILEHEADER), 1, file);
fread(&bmih, sizeof(BITMAPINFOHEADER), 1, file);
_Width = bmih.biWidth;
_Height = abs(bmih.biHeight);
if(bmih.biBitCount == 32)
{
Allocate(_Width, _Height);
fread(_Data, sizeof(RGBColor), _Width * _Height, file);
}
else if(bmih.biBitCount == 24)
{
Allocate(_Width, _Height);
UINT Pitch = _Width * 3;
UINT ExcessPitch = 0;
while(double(Pitch / 4) != double(Pitch) / 4.0)
{
Pitch++;
ExcessPitch++;
}
unsigned char *DataStore = new unsigned char[Pitch*_Height];
fread(DataStore, 1, Pitch*_Height, file);
UINT CurDataPos = 0;
for(UINT i=0;i<_Height;i++)
{
for(UINT i2=0;i2<_Width;i2++)
{
RGBColor CurColor;
CurColor.b = DataStore[CurDataPos++];
CurColor.g = DataStore[CurDataPos++];
CurColor.r = DataStore[CurDataPos++];
CurColor.a = 0;
_Data[i*_Width+i2] = CurColor;
}
CurDataPos += ExcessPitch;
}
delete[] DataStore;
}
else
{
SignalError("Invalid bit count");
}
fclose(file);
}
void Bitmap::LoadSDL(const String &Filename)
{
#ifdef USE_SDL
SDL_Surface *Image = IMG_Load(Filename.CString());
PersistentAssert(Image != NULL, String("SDL IMG_Load failed on ") + Filename);
SDL_Surface *FormattedImage = SDL_CreateRGBSurface(SDL_SWSURFACE, 1, 1, 32, 0, 0, 0, 0);
Utility::Swap(FormattedImage->format->Rmask, FormattedImage->format->Bmask);
SDL_Surface *ConvertedImage = SDL_ConvertSurface(Image, FormattedImage->format, SDL_SWSURFACE);
PersistentAssert(ConvertedImage != NULL, String("SDL_ConvertSurface failed on ") + Filename);
Allocate(ConvertedImage->w, ConvertedImage->h);
for(UINT y = 0; y < _Height; y++)
{
memcpy(_Data + _Width * y, ((RGBColor *)ConvertedImage->pixels) + _Width * (_Height - 1 - y), sizeof(RGBColor) * _Width);
}
SDL_FreeSurface(ConvertedImage);
SDL_FreeSurface(FormattedImage);
SDL_FreeSurface(Image);
#else
PersistentSignalError("LoadSDL called without USE_SDL");
#endif
}
#ifdef USE_PNG
struct PNGDirectMemoryIORead
{
const Vector<BYTE> *Buffer;
UINT Offset;
};
void Bitmap::PNGReadFromBuffer(png_structp png_ptr, png_bytep data, png_size_t length)
{
PNGDirectMemoryIORead *ReadInfo = (PNGDirectMemoryIORead *)png_get_io_ptr(png_ptr);
memcpy(data, ReadInfo->Buffer->CArray() + ReadInfo->Offset, length);
ReadInfo->Offset += length;
}
struct PNGDirectMemoryIOWrite
{
Vector<BYTE> *Buffer;
};
void Bitmap::PNGWriteToBuffer(png_structp png_ptr, png_bytep data, png_size_t length)
{
PNGDirectMemoryIOWrite *WriteInfo = (PNGDirectMemoryIOWrite *)png_get_io_ptr(png_ptr);
UINT StartLength = WriteInfo->Buffer->Length();
WriteInfo->Buffer->ReSize(StartLength + length);
memcpy(WriteInfo->Buffer->CArray() + StartLength, data, length);
}
void Bitmap::PNGFlushBuffer(png_structp png_ptr)
{
}
#endif
void Bitmap::LoadPNG(const String &Filename)
{
#ifdef USE_PNG
FILE *File;
File = fopen(Filename.CString(), "rb");
PersistentAssert(File != NULL, String("File open for LoadPNG failed: ") + String(Filename));
png_structp PngRead = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
PersistentAssert(PngRead != NULL, "png_create_read_struct failed.");
png_infop PngInfo = png_create_info_struct(PngRead);
PersistentAssert(PngInfo != NULL, "png_create_info_struct failed.");
png_init_io(PngRead, File);
PNGCompleteRead(PngRead, PngInfo, Filename);
fclose(File);
#else
SignalError("LoadPNG called without USE_PNG");
#endif
}
#ifdef USE_PNG
void Bitmap::LoadPNGFromMemory(const Vector<BYTE> &Buffer)
{
png_structp PngRead = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
Assert(PngRead != NULL, "png_create_read_struct failed.");
png_infop PngInfo = png_create_info_struct(PngRead);
Assert(PngInfo != NULL, "png_create_info_struct failed.");
PNGDirectMemoryIORead ReadInfo;
ReadInfo.Buffer = &Buffer;
ReadInfo.Offset = 0;
png_set_read_fn(PngRead, (void *)&ReadInfo, Bitmap::PNGReadFromBuffer);
PNGCompleteRead(PngRead, PngInfo, "Memory");
}
void Bitmap::PNGCompleteRead(png_structp PngRead, png_infop PngInfo, const String &Filename)
{
png_read_png(PngRead, PngInfo, PNG_TRANSFORM_PACKING | PNG_TRANSFORM_EXPAND, NULL);
png_bytepp RowPointers = png_get_rows(PngRead, PngInfo);
Assert(RowPointers != NULL, "png_get_rows failed.");
Allocate(PngRead->width, PngRead->height);
if(PngRead->channels == 3 && PngRead->pixel_depth == 24)
{
for(UINT y = 0; y < _Height; y++)
{
png_bytep CurRow = RowPointers[_Height - 1 - y];
for(UINT x = 0; x < _Width; x++)
{
(*this)[y][x].r = CurRow[x * 3 + 0];
(*this)[y][x].g = CurRow[x * 3 + 1];
(*this)[y][x].b = CurRow[x * 3 + 2];
(*this)[y][x].a = 0;
}
}
}
else if(PngRead->channels == 4 && PngRead->pixel_depth == 32)
{
for(UINT y = 0; y < _Height; y++)
{
png_bytep CurRow = RowPointers[_Height - 1 - y];
for(UINT x = 0; x < _Width; x++)
{
(*this)[y][x].r = CurRow[x * 4 + 0];
(*this)[y][x].g = CurRow[x * 4 + 1];
(*this)[y][x].b = CurRow[x * 4 + 2];
(*this)[y][x].a = CurRow[x * 4 + 3];
}
}
}
else if(PngRead->channels == 1 && PngRead->pixel_depth == 8)
{
for(UINT y = 0; y < _Height; y++)
{
png_bytep CurRow = RowPointers[_Height - 1 - y];
for(UINT x = 0; x < _Width; x++)
{
BYTE C = CurRow[x];
(*this)[y][x] = RGBColor(C, C, C, 0);
}
}
}
else
{
Console::WriteLine(String("Unsupported channel # / pixel depth in ") + Filename + String("; ") +
String(PngRead->channels) + String(" channels ") + String(PngRead->pixel_depth) +
String(" bits per pixel"));
Clear(RGBColor::Magenta);
}
png_destroy_read_struct(&PngRead, &PngInfo, NULL);
}
#endif
void Bitmap::SavePNG(const String &Filename) const
{
#ifdef USE_PNG
BitmapSaveOptions Options;
SavePNG(Filename, Options);
#else
SignalError("SavePNG called without USE_PNG");
#endif
}
#ifdef USE_PNG
void Bitmap::SavePNG(const String &Filename, const BitmapSaveOptions &Options) const
{
PersistentAssert(_Width > 0 && _Height > 0, "Saving empty image");
FILE *File;
File = fopen(Filename.CString(), "wb");
PersistentAssert(File != NULL, String("File open for SavePNG failed: ") + Filename);
png_structp PngWrite = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
PersistentAssert(PngWrite != NULL, "png_create_write_struct failed.");
png_infop PngInfo = png_create_info_struct(PngWrite);
PersistentAssert(PngInfo != NULL, "png_create_info_struct failed.");
png_init_io(PngWrite, File);
PNGCompleteWrite(PngWrite, PngInfo, Options);
fclose(File);
}
void Bitmap::SavePNGToMemory(Vector<BYTE> &Buffer) const
{
BitmapSaveOptions Options;
SavePNGToMemory(Buffer, Options);
}
void Bitmap::SavePNGToMemory(Vector<BYTE> &Buffer, const BitmapSaveOptions &Options) const
{
Buffer.FreeMemory();
png_structp PngWrite = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
Assert(PngWrite != NULL, "png_create_write_struct failed");
png_infop PngInfo = png_create_info_struct(PngWrite);
Assert(PngInfo != NULL, "png_create_info_struct failed");
PNGDirectMemoryIORead WriteInfo;
WriteInfo.Buffer = &Buffer;
png_set_write_fn(PngWrite, (void *)&WriteInfo, Bitmap::PNGWriteToBuffer, Bitmap::PNGFlushBuffer);
PNGCompleteWrite(PngWrite, PngInfo, Options);
}
void Bitmap::PNGCompleteWrite(png_structp PngWrite, png_infop PngInfo, const BitmapSaveOptions &Options) const
{
UINT LocalWidth = _Width;
UINT LocalHeight = _Height;
if(Options.Region.Max != Vec2i::Origin)
{
LocalWidth = Options.Region.Dimensions().x;
LocalHeight = Options.Region.Dimensions().y;
}
if(Options.SaveAlpha)
{
png_set_IHDR(PngWrite, PngInfo, LocalWidth, LocalHeight, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
}
else
{
png_set_IHDR(PngWrite, PngInfo, LocalWidth, LocalHeight, 8, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
}
png_set_PLTE(PngWrite, PngInfo, NULL, 0);
png_set_gAMA(PngWrite, PngInfo, 0.0);
png_color_8 ColorInfo;
ColorInfo.alpha = 0;
UINT BytesPerPixel = 3;
if(Options.SaveAlpha)
{
ColorInfo.alpha = 8;
BytesPerPixel = 4;
}
ColorInfo.red = 8;
ColorInfo.green = 8;
ColorInfo.blue = 8;
ColorInfo.gray = 0;
png_set_sBIT(PngWrite, PngInfo, &ColorInfo);
UINT ScratchSizeNeeded = (sizeof(png_bytep) + sizeof(png_byte) * BytesPerPixel * LocalWidth) * LocalHeight;
BYTE *ScratchSpace = Options.ScratchSpace;
BYTE *ScratchSpaceStart = NULL;
if(Options.ScratchSpaceSize < ScratchSizeNeeded)
{
ScratchSpace = new BYTE[ScratchSizeNeeded];
ScratchSpaceStart = ScratchSpace;
}
png_bytep *RowPointers = (png_bytep *)ScratchSpace;
ScratchSpace += sizeof(png_bytep) * LocalHeight;
for(UINT y = 0; y < LocalHeight; y++)
{
BYTE *DestRowStart = ScratchSpace;
ScratchSpace += sizeof(png_byte) * BytesPerPixel * LocalWidth;
RowPointers[LocalHeight - 1 - y] = DestRowStart;
const RGBColor *SrcRowStart = (*this)[y + Options.Region.Min.y];
UINT DestRowStartOffset = 0;
if(Options.UseBGR)
{
if(Options.SaveAlpha)
{
for(UINT x = 0; x < LocalWidth; x++)
{
RGBColor Color = SrcRowStart[x + Options.Region.Min.x];
DestRowStart[DestRowStartOffset++] = Color.b;
DestRowStart[DestRowStartOffset++] = Color.g;
DestRowStart[DestRowStartOffset++] = Color.r;
DestRowStart[DestRowStartOffset++] = Color.a;
}
}
else
{
for(UINT x = 0; x < LocalWidth; x++)
{
RGBColor Color = SrcRowStart[x + Options.Region.Min.x];
DestRowStart[DestRowStartOffset++] = Color.b;
DestRowStart[DestRowStartOffset++] = Color.g;
DestRowStart[DestRowStartOffset++] = Color.r;
}
}
}
else
{
if(Options.SaveAlpha)
{
for(UINT x = 0; x < LocalWidth; x++)
{
RGBColor Color = SrcRowStart[x + Options.Region.Min.x];
DestRowStart[DestRowStartOffset++] = Color.r;
DestRowStart[DestRowStartOffset++] = Color.g;
DestRowStart[DestRowStartOffset++] = Color.b;
DestRowStart[DestRowStartOffset++] = Color.a;
}
}
else
{
for(UINT x = 0; x < LocalWidth; x++)
{
RGBColor Color = SrcRowStart[x + Options.Region.Min.x];
DestRowStart[DestRowStartOffset++] = Color.r;
DestRowStart[DestRowStartOffset++] = Color.g;
DestRowStart[DestRowStartOffset++] = Color.b;
}
}
}
}
png_set_rows(PngWrite, PngInfo, RowPointers);
png_write_png(PngWrite, PngInfo, NULL, NULL);
png_destroy_write_struct(&PngWrite, &PngInfo);
if(ScratchSpaceStart != NULL)
{
delete[] ScratchSpaceStart;
}
}
#endif
void Bitmap::SavePPM(const String &Filename) const
{
String FilenameCopy = Filename;
ofstream file(FilenameCopy.CString());
file << "P3" << endl;
file << "# PPM saved to " << FilenameCopy.CString() << endl;
file << _Width << ' ' << _Height << endl;
file << 255 << endl;
for(UINT i2=0;i2<_Height;i2++)
{
for(UINT i=0;i<_Width;i++)
{
file << int((*this)[i2][i].r) << ' ' << int((*this)[i2][i].g) << ' ' << int((*this)[i2][i].b) << endl;
}
}
}
void Bitmap::SavePGM(const String &Filename, int Channel) const
{
FILE *File;
File = fopen(Filename.CString(), "wb");
PersistentAssert(File != NULL, "File open for SavePNG failed.");
fprintf(File, "P5\n%d %d\n255\n", _Width, _Height);
for (UINT y = 0; y < _Height; y++)
{
for (UINT x = 0; x < _Width; x++)
{
unsigned char Value;
RGBColor Color = (*this)[y][x];
switch(Channel)
{
case 0:
Value = Color.r;
break;
case 1:
Value = Color.g;
break;
case 2:
Value = Color.b;
break;
case 3:
Value = Color.a;
break;
case 4:
default:
Value = Utility::BoundToByte((int(Color.r) + int(Color.g) + int(Color.b)) / 3);
}
fputc(Value, File);
}
}
}
void Bitmap::LoadAlphaChannelAsGrayscale()
{
for(UINT y = 0; y < _Height; y++)
{
for(UINT x = 0; x < _Width; x++)
{
RGBColor &C = (*this)[y][x];
C.r = C.a;
C.g = C.a;
C.b = C.a;
}
}
}
void Bitmap::FlipBlueAndRed()
{
for(UINT y = 0; y < _Height; y++)
{
for(UINT x = 0; x < _Width; x++)
{
RGBColor &C = (*this)[y][x];
unsigned char Temp = C.r;
C.r = C.b;
C.b = Temp;
}
}
}
void Bitmap::Clear()
{
memset(_Data, 0, sizeof(RGBColor) * _Width * _Height);
}
void Bitmap::Clear(const RGBColor &Color)
{
for(UINT i=0;i<_Width;i++)
{
_Data[i] = Color;
}
for(UINT i2=1;i2<_Height;i2++)
{
memcpy(&_Data[i2*_Width], &_Data[0], sizeof(RGBColor) * _Width);
}
}
void Bitmap::BltTo(Bitmap &B, int TargetX, int TargetY) const
{
BltTo(B, TargetX, TargetY, 0, 0, _Width, _Height);
}
void Bitmap::BltToNoClipMemcpy(Bitmap &B, UINT TargetX, UINT TargetY) const
{
const UINT Height = _Height;
const UINT Width = _Width;
for(UINT y = 0; y < Height; y++)
{
const UINT CurTargetY = y + TargetY;
memcpy(B._Data + (CurTargetY * B._Width + TargetX), _Data + y * _Width, sizeof(RGBColor) * Width);
}
}
void Bitmap::BltTo(Bitmap &B, int TargetX, int TargetY, int SourceX, int SourceY, int Width, int Height) const
{
const int BHeight = B._Height;
const int BWidth = B._Width;
for(int y = 0; y < Height; y++)
{
const int CurTargetY = y + TargetY;
const int CurSourceY = y + SourceY;
if(CurTargetY >= 0 && CurTargetY < BHeight &&
CurSourceY >= 0 && CurSourceY < int(_Height))
{
for(int x = 0; x < Width; x++)
{
const int CurTargetX = x + TargetX;
const int CurSourceX = x + SourceX;
if(CurTargetX >= 0 && CurTargetX < BWidth &&
CurSourceX >= 0 && CurSourceX < int(_Width))
{
B[CurTargetY][CurTargetX] = (*this)[CurSourceY][CurSourceX];
}
}
}
}
}
void Bitmap::StretchBltTo(Bitmap &B, const Rectangle2i &TargetRect, const Rectangle2i &SourceRect, SamplingType Sampling) const
{
StretchBltTo(B,
TargetRect.Min.x, TargetRect.Min.y, TargetRect.Width(), TargetRect.Height(),
SourceRect.Min.x, SourceRect.Min.y, SourceRect.Width(), SourceRect.Height(), Sampling);
}
void Bitmap::StretchBltTo(Bitmap &B, int TargetX, int TargetY, int TargetWidth, int TargetHeight, int SourceX, int SourceY, int SourceWidth, int SourceHeight, SamplingType Sampling) const
{
if(Sampling == SamplingPoint)
{
const float RatioX = float(SourceWidth) / float(TargetWidth);
const float RatioY = float(SourceHeight) / float(TargetHeight);
const float SourceXOffset = float(SourceX + 0.5f * RatioX) + 0.5f;
const float SourceYOffset = float(SourceY + 0.5f * RatioY) + 0.5f;
const int Width = _Width;
const int Height = _Height;
for(int y = 0; y < TargetHeight; y++)
{
for(int x = 0; x < TargetWidth; x++)
{
int XMiddle = int(x * RatioX + SourceXOffset);
int YMiddle = int(y * RatioY + SourceYOffset);
if(XMiddle >= 0 && XMiddle < Width && YMiddle >= 0 && YMiddle < Height)
{
B._Data[(y + TargetY) * B._Width + x + TargetX] = _Data[YMiddle * _Width + XMiddle];
}
}
}
}
else if(Sampling == SamplingLinear)
{
for(int y = 0; y < TargetHeight; y++)
{
for(int x = 0; x < TargetWidth; x++)
{
float XStart = Math::LinearMap(0.0f, float(TargetWidth), float(SourceX), float(SourceX + SourceWidth ), float(x + 0));
float XEnd = Math::LinearMap(0.0f, float(TargetWidth), float(SourceX), float(SourceX + SourceWidth ), float(x + 1));
float YStart = Math::LinearMap(0.0f, float(TargetHeight), float(SourceY), float(SourceY + SourceHeight), float(y + 0));
float YEnd = Math::LinearMap(0.0f, float(TargetHeight), float(SourceY), float(SourceY + SourceHeight), float(y + 1));
B[y + TargetY][x + TargetX] = AverageColorOverRegion(Rectangle2f(Vec2f(XStart, YStart), Vec2f(XEnd, YEnd)));
}
}
}
}
RGBColor Bitmap::AverageColorOverRegion(const Rectangle2f &Region) const
{
Vec3f Result = Vec3f::Origin;
int XStart = Math::Max(Math::Floor(Region.Min.x), 0);
int YStart = Math::Max(Math::Floor(Region.Min.y), 0);
int XEnd = Math::Min(Math::Ceiling(Region.Max.x), int(_Width) - 1);
int YEnd = Math::Min(Math::Ceiling(Region.Max.y), int(_Height) - 1);
const RGBColor *Data = _Data;
const UINT Width = _Width;
UINT Count = 0;
for(int y = YStart; y <= YEnd; y++)
{
for(int x = XStart; x <= XEnd; x++)
{
Count++;
Result += Vec3f(Data[y * Width + x]);
}
}
return RGBColor(Result * (1.0f / float(Count)));
}
void Bitmap::LoadGrid(const Grid<float> &Values)
{
float Min = Values(0, 0);
float Max = Values(0, 0);
for(UINT y = 0; y < Values.Rows(); y++)
{
for(UINT x = 0; x < Values.Cols(); x++)
{
if(Values(y, x) != 0.0f)
{
if(Min == 0.0f)
{
Min = Values(y, x);
}
Min = Math::Min(Min, Values(y, x));
}
Max = Math::Max(Max, Values(y, x));
}
}
LoadGrid(Values, Min, Max);
}
void Bitmap::LoadGrid(const Grid<float> &Values, float Min, float Max)
{
Allocate(Values.Cols(), Values.Rows());
for(UINT y = 0; y < _Height; y++)
{
for(UINT x = 0; x < _Width; x++)
{
BYTE Intensity = 0;
if(Max != Min)
{
Intensity = Utility::BoundToByte(Math::LinearMap(Min, Max, 0.0f, 255.0f, Values(y, x)));
}
if(Values(y, x) == 0.0f)
{
(*this)[y][x] = RGBColor::Magenta;
}
else
{
(*this)[y][x] = RGBColor(Intensity, Intensity, Intensity);
}
}
}
}
void Bitmap::ReplaceColor(RGBColor CurrentColor, RGBColor NewColor)
{
for(UINT y = 0; y < _Height; y++)
{
for(UINT x = 0; x < _Width; x++)
{
if((*this)[y][x] == CurrentColor)
{
(*this)[y][x] = NewColor;
}
}
}
}
UINT Bitmap::Hash32() const
{
return Utility::Hash32((BYTE *)_Data, sizeof(RGBColor) * _Width * _Height) + (_Width * 785 + _Height * 97);
}
UINT64 Bitmap::Hash64() const
{
return Utility::Hash64((BYTE *)_Data, sizeof(RGBColor) * _Width * _Height) + (_Width * 785 + _Height * 97);
}
UINT Bitmap::CountPixelsWithColor(RGBColor Color) const
{
UINT Result = 0;
for(UINT y = 0; y < _Height; y++)
{
for(UINT x = 0; x < _Width; x++)
{
if(_Data[y * _Width + x] == Color)
{
Result++;
}
}
}
return Result;
}
bool Bitmap::Monochrome(RGBColor Color) const
{
for(UINT y = 0; y < _Height; y++)
{
for(UINT x = 0; x < _Width; x++)
{
if(_Data[y * _Width + x] != Color)
{
return false;
}
}
}
return true;
}
bool Bitmap::MonochromeIncludingAlpha(RGBColor Color) const
{
for(UINT y = 0; y < _Height; y++)
{
for(UINT x = 0; x < _Width; x++)
{
const RGBColor C = _Data[y * _Width + x];
if(C.r != Color.r || C.g != Color.g || C.b != Color.b || C.a != Color.a)
{
return false;
}
}
}
return true;
}
bool Bitmap::PerfectMatch(const Bitmap &Bmp) const
{
if(Bmp.Dimensions() != Dimensions())
{
return false;
}
for(UINT y = 0; y < _Height; y++)
{
for(UINT x = 0; x < _Width; x++)
{
if ( (*this)[y][x].r != Bmp[y][x].r ||
(*this)[y][x].g != Bmp[y][x].g ||
(*this)[y][x].b != Bmp[y][x].b)
{
return false;
}
}
}
return true;
}
void Bitmap::FlipHorizontal()
{
Bitmap BTemp = *this;
for(UINT y = 0; y < _Height; y++)
{
for(UINT x = 0; x < _Width; x++)
{
(*this)[y][x] = BTemp[y][_Width - 1 - x];
}
}
}
void Bitmap::FlipVertical()
{
Bitmap BTemp = *this;
for(UINT y = 0; y < _Height; y++)
{
for(UINT x = 0; x < _Width; x++)
{
(*this)[y][x] = BTemp[_Height - 1 - y][x];
}
}
}
#if 0
void Bitmap::DrawLine(int X0, int Y0, int X1, int Y1)
{
const UINT NumLevels = 256;
const UINT IntensityBits = 8;
const UINT BaseColor = 0;
unsigned short IntensityShift, ErrorAdj, ErrorAcc;
unsigned short ErrorAccTemp, Weighting, WeightingComplementMask;
short DeltaX, DeltaY, Temp, XDir;
X0 = Utility::Bound(X0, 0, int(_Width) - 1);
X1 = Utility::Bound(X1, 0, int(_Width) - 1);
Y0 = Utility::Bound(Y0, 0, int(_Height) - 1);
Y1 = Utility::Bound(Y1, 0, int(_Height) - 1);
if (Y0 > Y1)
{
Temp = Y0; Y0 = Y1; Y1 = Temp;
Temp = X0; X0 = X1; X1 = Temp;
}
(*this)[Y0][X0] = RGBColor::Black;
if ((DeltaX = X1 - X0) >= 0)
{
XDir = 1;
}
else
{
XDir = -1;
DeltaX = -DeltaX;
}
if ((DeltaY = Y1 - Y0) == 0)
{
while (DeltaX-- != 0)
{
X0 += XDir;
(*this)[Y0][X0] = RGBColor::Black;
}
return;
}
if (DeltaX == 0) {
do
{
Y0++;
(*this)[Y0][X0] = RGBColor::Black;
} while (--DeltaY != 0);
return;
}
if (DeltaX == DeltaY) {
do
{
X0 += XDir;
Y0++;
(*this)[Y0][X0] = RGBColor::Black;
} while (--DeltaY != 0);
return;
}
ErrorAcc = 0;
IntensityShift = 16 - IntensityBits;
WeightingComplementMask = NumLevels - 1;
if (DeltaY > DeltaX)
{
ErrorAdj = unsigned short(((unsigned long) DeltaX << 16) / (unsigned long) DeltaY);
while (--DeltaY) {
ErrorAccTemp = ErrorAcc;
ErrorAcc += ErrorAdj;
if (ErrorAcc <= ErrorAccTemp) {
X0 += XDir;
}
Y0++;
Weighting = ErrorAcc >> IntensityShift;
unsigned char LVal = (BaseColor + Weighting);
(*this)[Y0][X0] = RGBColor(LVal, LVal, LVal);
LVal = BaseColor + (Weighting ^ WeightingComplementMask);
(*this)[Y0][X0 + XDir] = RGBColor(LVal, LVal, LVal);
}
(*this)[Y1][X1] = RGBColor::Black;
return;
}
ErrorAdj = unsigned short(((unsigned long) DeltaY << 16) / (unsigned long) DeltaX);
while (--DeltaX) {
ErrorAccTemp = ErrorAcc;
ErrorAcc += ErrorAdj;
if (ErrorAcc <= ErrorAccTemp) {
Y0++;
}
X0 += XDir;
Weighting = ErrorAcc >> IntensityShift;
unsigned char LVal = (BaseColor + Weighting);
(*this)[Y0][X0] = RGBColor(LVal, LVal, LVal);
LVal = BaseColor + (Weighting ^ WeightingComplementMask);
(*this)[Y0 + 1][X0] = RGBColor(LVal, LVal, LVal);
}
(*this)[Y1][X1] = RGBColor::Black;
}
#endif
#ifdef USE_D3D9
void Bitmap::LoadFromSurface(LPDIRECT3DSURFACE9 Surface)
{
D3DSURFACE_DESC Desc;
D3DLOCKED_RECT LockedRect;
D3DAlwaysValidate(Surface->GetDesc(&Desc), "GetDesc");
PersistentAssert(Desc.Format == D3DFMT_A8R8G8B8 ||
Desc.Format == D3DFMT_X8R8G8B8, "Invalid surface format");
Allocate(Desc.Width, Desc.Height);
D3DAlwaysValidate(Surface->LockRect(&LockedRect, NULL, 0), "LockRect");
BYTE *_Data = (BYTE *)LockedRect.pBits;
if(Desc.Format == D3DFMT_A8R8G8B8 || Desc.Format == D3DFMT_X8R8G8B8)
{
for(UINT y = 0; y < _Height; y++)
{
RGBColor *RowStart = (RGBColor *)(_Data + y * LockedRect.Pitch);
memcpy((*this)[_Height - 1 - y], RowStart, sizeof(RGBColor) * _Width);
}
}
D3DAlwaysValidate(Surface->UnlockRect(), "UnlockRect");
}
UINT Bitmap::SaveDelta(const Bitmap &Bmp, const String &Filename) const
{
const UINT Width = _Width;
const UINT Height = _Height;
const UINT PixelCount = Width * Height;
const RGBColor *MyData = _Data;
const RGBColor *PrevData = Bmp._Data;
PersistentAssert(Width == Bmp.Width() && Height == Bmp.Height(), "Invalid dimensions");
static BYTE *Storage = NULL;
if(Storage == NULL)
{
Storage = new BYTE[PixelCount * sizeof(RGBColor) * 2];
}
BYTE *CurStorageByte = Storage;
UINT PixelIndex = 0;
while(PixelIndex < PixelCount)
{
const WORD MyColor16 = RGBColor16FromRGBColor32(*MyData);
const WORD PrevColor16 = RGBColor16FromRGBColor32(*PrevData);
if(MyColor16 == PrevColor16)
{
BYTE *Counter = CurStorageByte;
CurStorageByte++;
*Counter = 255;
for(UINT MatchIndex = 0; MatchIndex < 128; MatchIndex++)
{
const WORD MatchMyColor16 = RGBColor16FromRGBColor32(*(MyData));
const WORD MatchPrevColor16 = RGBColor16FromRGBColor32(*(PrevData));
if(MatchMyColor16 == MatchPrevColor16)
{
PrevData++;
MyData++;
PixelIndex++;
(*Counter)++;
}
else
{
break;
}
}
}
else
{
BYTE *Counter = CurStorageByte;
CurStorageByte++;
*Counter = 127;
for(UINT MatchIndex = 0; MatchIndex < 128; MatchIndex++)
{
RGBColor MatchMyColor = *(MyData);
const WORD MatchMyColor16 = RGBColor16FromRGBColor32(MatchMyColor);
const WORD MatchPrevColor16 = RGBColor16FromRGBColor32(*(PrevData));
if(MatchMyColor16 != MatchPrevColor16)
{
{
*((WORD*)CurStorageByte) = RGBColor16FromRGBColor32(MatchMyColor);
CurStorageByte += 2;
}
PrevData++;
MyData++;
PixelIndex++;
(*Counter)++;
}
else
{
break;
}
}
}
}
FILE *File = Utility::CheckedFOpen(Filename.CString(), "wb");
fwrite(Storage, CurStorageByte - Storage, 1, File);
fclose(File);
return (CurStorageByte - Storage);
}
void Bitmap::LoadDelta(const Bitmap &Bmp, const String &Filename)
{
*this = Bmp;
const UINT Width = _Width;
const UINT Height = _Height;
const UINT PixelCount = Width * Height;
RGBColor *MyData = _Data;
const RGBColor *PrevData = Bmp._Data;
Vector<BYTE> Storage;
Utility::GetFileData(Filename, Storage);
BYTE *CurStorageByte = Storage.CArray();
UINT PixelIndex = 0;
while(PixelIndex < PixelCount)
{
BYTE Counter = *CurStorageByte;
CurStorageByte++;
if(Counter >= 0 && Counter < 128)
{
PixelIndex += Counter + 1;
}
else
{
Counter -= 128;
RGBColor PrevColor;
for(UINT MatchIndex = 0; MatchIndex <= Counter; MatchIndex++)
{
{
PrevColor = RGBColor32FromRGBColor16(*((WORD*)CurStorageByte));
CurStorageByte += 2;
MyData[PixelIndex] = PrevColor;
}
PixelIndex++;
}
}
}
}
#if 0
void Bitmap::SaveSurfaceToZLIB(LPDIRECT3DSURFACE9 Surface, const String &Filename, const BitmapSaveOptions &Options)
{
D3DSURFACE_DESC Desc;
D3DLOCKED_RECT LockedRect;
D3DAlwaysValidate(Surface->GetDesc(&Desc), "GetDesc");
PersistentAssert(Desc.Format == D3DFMT_A8R8G8B8 ||
Desc.Format == D3DFMT_X8R8G8B8, "Invalid surface format");
D3DAlwaysValidate(Surface->LockRect(&LockedRect, NULL, 0), "LockRect");
const UINT Width = Desc.Width;
const UINT Height = Desc.Height;
BYTE *_Data = (BYTE *)LockedRect.pBits;
int Result;
z_stream Stream;
const UINT InputBytes = Width * Height * sizeof(RGBColor);
BYTE *CompressedStream = new BYTE[InputBytes + 1024];
Stream.zalloc = Z_NULL;
Stream.zfree = Z_NULL;
Stream.opaque = Z_NULL;
Stream.avail_in = InputBytes;
Stream.next_in = _Data;
Stream.data_type = Z_BINARY;
Stream.avail_out = InputBytes;
Stream.next_out = CompressedStream;
const int Level = 6;
Result = deflateInit2(&Stream, Level, Z_DEFLATED, 8, 8, Z_HUFFMAN_ONLY);
PersistentAssert(Result == Z_OK, "deflateInit failed");
Result = deflate(&Stream, Z_FINISH);
PersistentAssert(Result == Z_STREAM_END, "deflate failed");
deflateEnd(&Stream);
delete[] CompressedStream;
D3DAlwaysValidate(Surface->UnlockRect(), "UnlockRect");
}
#endif
#ifdef USE_PNG
void Bitmap::SaveSurfaceToPNG(LPDIRECT3DSURFACE9 Surface, const String &Filename, const BitmapSaveOptions &Options)
{
D3DSURFACE_DESC Desc;
D3DLOCKED_RECT LockedRect;
D3DAlwaysValidate(Surface->GetDesc(&Desc), "GetDesc");
PersistentAssert(Desc.Format == D3DFMT_A8R8G8B8 ||
Desc.Format == D3DFMT_X8R8G8B8, "Invalid surface format");
D3DAlwaysValidate(Surface->LockRect(&LockedRect, NULL, 0), "LockRect");
const UINT Width = Desc.Width;
const UINT Height = Desc.Height;
BYTE *_Data = (BYTE *)LockedRect.pBits;
FILE *File;
File = fopen(Filename.CString(), "wb");
PersistentAssert(File != NULL, "File open for SavePNG failed");
png_structp PngWrite = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
PersistentAssert(PngWrite != NULL, "png_create_write_struct failed");
png_infop PngInfo = png_create_info_struct(PngWrite);
PersistentAssert(PngInfo != NULL, "png_create_info_struct failed");
png_init_io(PngWrite, File);
png_set_IHDR(PngWrite, PngInfo, Width, Height, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
png_set_PLTE(PngWrite, PngInfo, NULL, 0);
png_set_gAMA(PngWrite, PngInfo, 0.0);
png_color_8 ColorInfo;
ColorInfo.alpha = 0;
UINT BytesPerPixel = 3;
if(Options.SaveAlpha)
{
ColorInfo.alpha = 8;
BytesPerPixel = 4;
}
ColorInfo.red = 8;
ColorInfo.green = 8;
ColorInfo.blue = 8;
ColorInfo.gray = 0;
png_set_sBIT(PngWrite, PngInfo, &ColorInfo);
UINT ScratchSizeNeeded = sizeof(png_bytep) * Height;
BYTE *ScratchSpace = Options.ScratchSpace;
BYTE *ScratchSpaceStart = NULL;
if(Options.ScratchSpaceSize < ScratchSizeNeeded)
{
ScratchSpace = new BYTE[ScratchSizeNeeded];
ScratchSpaceStart = ScratchSpace;
}
png_bytep *RowPointers = (png_bytep *)ScratchSpace;
for(UINT y = 0; y < Height; y++)
{
RowPointers[y] = (_Data + y * LockedRect.Pitch);
}
png_set_rows(PngWrite, PngInfo, RowPointers);
png_write_png(PngWrite, PngInfo, NULL, NULL);
png_destroy_write_struct(&PngWrite, &PngInfo);
if(ScratchSpaceStart != NULL)
{
delete[] ScratchSpaceStart;
}
D3DAlwaysValidate(Surface->UnlockRect(), "UnlockRect");
fclose(File);
}
#endif
#endif
Bitmap operator + (const Bitmap &L, const Bitmap &R)
{
Assert(L.Width() == R.Width() && L.Height() == R.Height(), "Invalid dimensions in Bitmap operator +");
Bitmap Result = L;
for(UINT y = 0; y < Result.Height(); y++)
{
for(UINT x = 0; x < Result.Width(); x++)
{
Result[y][x] += R[y][x];
}
}
return Result;
}