/* Bitmap.cpp Written by Matthew Fisher A bitmap class (a 2D array of RGBColor's) Rather self-explanitory, can only save and load a few primitive formats. See Bitmap.h for a complete description */ 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; //stores information about the file format BITMAPINFOHEADER bmih; //stores information about the bitmap FILE *file; //stores file pointer //create bitmap file header ((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; //create bitmap information header 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; //save all header and bitmap information into file 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; //stores information about the file format BITMAPINFOHEADER bmih; //stores information about the bitmap FILE *file; //stores file pointer //open the file and read in the headers 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 space for the read operation Allocate(_Width, _Height); //save all header and bitmap information into file fread(_Data, sizeof(RGBColor), _Width * _Height, file); } else if(bmih.biBitCount == 24) { //Allocate space for the read operation 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, Image->w, Image->h, 32, 0, 0, 0, 0); SDL_Surface *FormattedImage = SDL_CreateRGBSurface(SDL_SWSURFACE, 1, 1, 32, 0, 0, 0, 0); /*SDL_PixelFormat Format; Format.palette = NULL; Format.BitsPerPixel = 32; Format.BytesPerPixel = 4; Format.*/ 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); //memcpy(_Data, ConvertedImage->pixels, sizeof(RGBColor) * _Width * _Height); 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 *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 *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 &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 { /*PersistentAssert(NULL, String("Unsupported channel # / pixel depth in ") + Filename + String("; ") + String(PngRead->channels) + String(" channels ") + String(PngRead->pixel_depth) + String(" bits per pixel"));*/ 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 &Buffer) const { BitmapSaveOptions Options; SavePNGToMemory(Buffer, Options); } void Bitmap::SavePNGToMemory(Vector &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_set_sRGB_gAMA_and_cHRM(PngWrite, PngInfo, PNG_sRGB_INTENT_PERCEPTUAL); 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 { //PPM is a very simple ASCII format String FilenameCopy = Filename; ofstream file(FilenameCopy.CString()); file << "P3" << endl; file << "# PPM saved to " << FilenameCopy.CString() << endl; file << _Width << ' ' << _Height << endl; file << 255 << endl; //maximum value of a component 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 &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 &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); } } } } /*BITMAP_HASH Bitmap::UnorientedHash() const { BITMAP_HASH Result = _Width + _Height + _Width * _Height + _Width * _Width * _Height; for(UINT y=0;y<_Height;y++) { for(UINT x=0;x<_Width;x++) { RGBColor C = (*this)[y][x]; Result += int(C.r) * 541 + int(C.g) * 978 + int(C.b) * 1024 + int(C.r) * int(C.g); } } return Result; }*/ 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 // // The following is taken from // http://www.suchit-tiwari.org/antialias.html // 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); /* Make sure the line runs top to bottom */ if (Y0 > Y1) { Temp = Y0; Y0 = Y1; Y1 = Temp; Temp = X0; X0 = X1; X1 = Temp; } /* Draw the initial pixel, which is always exactly intersected by the line and so needs no weighting */ (*this)[Y0][X0] = RGBColor::Black; if ((DeltaX = X1 - X0) >= 0) { XDir = 1; } else { XDir = -1; DeltaX = -DeltaX; /* make DeltaX positive */ } /* Special-case horizontal, vertical, and diagonal lines, which require no weighting because they go right through the center of every pixel */ if ((DeltaY = Y1 - Y0) == 0) { /* Horizontal line */ while (DeltaX-- != 0) { X0 += XDir; (*this)[Y0][X0] = RGBColor::Black; } return; } if (DeltaX == 0) { /* Vertical line */ do { Y0++; (*this)[Y0][X0] = RGBColor::Black; } while (--DeltaY != 0); return; } if (DeltaX == DeltaY) { /* Diagonal line */ do { X0 += XDir; Y0++; (*this)[Y0][X0] = RGBColor::Black; } while (--DeltaY != 0); return; } /* Line is not horizontal, diagonal, or vertical */ ErrorAcc = 0; /* initialize the line error accumulator to 0 */ /* # of bits by which to shift ErrorAcc to get intensity level */ IntensityShift = 16 - IntensityBits; /* Mask used to flip all bits in an intensity weighting, producing the result (1 - intensity weighting) */ WeightingComplementMask = NumLevels - 1; /* Is this an X-major or Y-major line? */ if (DeltaY > DeltaX) { /* Y-major line; calculate 16-bit fixed-point fractional part of a pixel that X advances each time Y advances 1 pixel, truncating the result so that we won't overrun the endpoint along the X axis */ ErrorAdj = unsigned short(((unsigned long) DeltaX << 16) / (unsigned long) DeltaY); /* Draw all pixels other than the first and last */ while (--DeltaY) { ErrorAccTemp = ErrorAcc; /* remember currrent accumulated error */ ErrorAcc += ErrorAdj; /* calculate error for next pixel */ if (ErrorAcc <= ErrorAccTemp) { /* The error accumulator turned over, so advance the X coord */ X0 += XDir; } Y0++; /* Y-major, so always advance Y */ /* The IntensityBits most significant bits of ErrorAcc give us the intensity weighting for this pixel, and the complement of the weighting for the paired pixel */ 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); } /* Draw the final pixel, which is always exactly intersected by the line and so needs no weighting */ (*this)[Y1][X1] = RGBColor::Black; return; } /* It's an X-major line; calculate 16-bit fixed-point fractional part of a pixel that Y advances each time X advances 1 pixel, truncating the result to avoid overrunning the endpoint along the X axis */ ErrorAdj = unsigned short(((unsigned long) DeltaY << 16) / (unsigned long) DeltaX); /* Draw all pixels other than the first and last */ while (--DeltaX) { ErrorAccTemp = ErrorAcc; /* remember currrent accumulated error */ ErrorAcc += ErrorAdj; /* calculate error for next pixel */ if (ErrorAcc <= ErrorAccTemp) { /* The error accumulator turned over, so advance the Y coord */ Y0++; } X0 += XDir; /* X-major, so always advance X */ /* The IntensityBits most significant bits of ErrorAcc give us the intensity weighting for this pixel, and the complement of the weighting for the paired pixel */ 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); } /* Draw the final pixel, which is always exactly intersected by the line and so needs no weighting */ (*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); //for(UINT x = 0; x < _Width; x++) //{ // (*this)[y][x] = RowStart[x]; //} } } 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; //const UINT ChromaSubsamplingRate = 3; //UINT ChromaSubsamplingIndex = ChromaSubsamplingRate - 1; for(UINT MatchIndex = 0; MatchIndex < 128; MatchIndex++) { RGBColor MatchMyColor = *(MyData); const WORD MatchMyColor16 = RGBColor16FromRGBColor32(MatchMyColor); const WORD MatchPrevColor16 = RGBColor16FromRGBColor32(*(PrevData)); if(MatchMyColor16 != MatchPrevColor16) { //ChromaSubsamplingIndex++; //if(ChromaSubsamplingIndex == ChromaSubsamplingRate) { //ChromaSubsamplingIndex = 0; *((WORD*)CurStorageByte) = RGBColor16FromRGBColor32(MatchMyColor); CurStorageByte += 2; } //else //{ // *CurStorageByte = (int(MatchMyColor.r) + int(MatchMyColor.g) + int(MatchMyColor.b)) / 3; // CurStorageByte++; //} 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 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; //const UINT ChromaSubsamplingRate = 3; //UINT ChromaSubsamplingIndex = ChromaSubsamplingRate - 1; RGBColor PrevColor; for(UINT MatchIndex = 0; MatchIndex <= Counter; MatchIndex++) { //ChromaSubsamplingIndex++; //if(ChromaSubsamplingIndex == ChromaSubsamplingRate) { //ChromaSubsamplingIndex = 0; PrevColor = RGBColor32FromRGBColor16(*((WORD*)CurStorageByte)); CurStorageByte += 2; MyData[PixelIndex] = PrevColor; } //else //{ // UINT L = (*CurStorageByte); // CurStorageByte++; // if(PrevColor.r == 0 && PrevColor.g == 0 && PrevColor.b == 0) // { // PrevColor = RGBColor(1, 1, 1); // } // Vec3f D = Vec3f::Normalize(Vec3f(PrevColor)); // D *= (3.0f / 255.0f) * L / (D.x + D.y + D.z); // UINT B = (int(RGBColor(D).r) + int(RGBColor(D).g) + int(RGBColor(D).b)) / 3; // MyData[PixelIndex] = RGBColor(D); //} 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 = deflateInit(&Stream, Level); 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"); //fclose(File); } #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; /*for(UINT y = 0; y < Height; y++) { RGBColor *RowStart = (RGBColor *)(_Data + y * LockedRect.Pitch); memcpy((*this)[Height - 1 - y], RowStart, sizeof(RGBColor) * Width); //for(UINT x = 0; x < _Width; x++) //{ // (*this)[y][x] = RowStart[x]; //} }*/ 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; }