#include #include #include #include "bmp.h" // ---------------------------------------------------------------- // Create bmp, size (width x height) // ---------------------------------------------------------------- Bmp::Bmp(int width, int height) : mWidth(width), mHeight(height) { for(int i = 0; i < mHeight; i ++) mImage.push_back(vector(mWidth, Types::Color())); } // ---------------------------------------------------------------- // Draw a pixel at (x, y) with color (red, green, blue) // ---------------------------------------------------------------- void Bmp::setPixel( int x, int y, unsigned char red, unsigned char green, unsigned char blue ) { bool doSet = true; if(x < 0 || x >= mWidth) { std::cout << "Invalid value: " << x << " (expected: 0 <= x < " << mWidth << "\n"; doSet = false; } if(y < 0 || y >= mHeight) { std::cout << "Invalid value: " << y << " (expected: 0 <= x < " << mHeight << "\n"; doSet = false; } if(doSet) mImage[y][x] = Types::Color(red, green, blue); } // ---------------------------------------------------------------- // Draw a pixel at (x, y) with color: theColor // ---------------------------------------------------------------- void Bmp::setPixel( int x, int y, const Types::Color &theColor ) { setPixel(x, y, theColor.mRed, theColor.mGreen, theColor.mBlue); } // ---------------------------------------------------------------- // Draw a rectangle at (x, y) with the specified width and // height using color: (red, green, blue) // ---------------------------------------------------------------- void Bmp::drawRectangle( int x, int y, int width, int height, unsigned char red, unsigned char green, unsigned char blue ) { drawLine(x, y, x + width, y, red, green, blue); drawLine(x + width, y, x + width, y + height, red, green, blue); drawLine(x + width, y + height, x, y + height, red, green, blue); drawLine(x, y + height, x, y, red, green, blue); } // ---------------------------------------------------------------- // Draw a rectangle at (x, y) with the specified width and // height using color: theColor // ---------------------------------------------------------------- void Bmp::drawRectangle( int x, int y, int width, int height, const Types::Color &theColor ) { drawRectangle( x, y, width, height, theColor.mRed, theColor.mGreen, theColor.mBlue ); } // ---------------------------------------------------------------- // Fill a rectangle at (x, y) with the specified width and // height using color: (red, green, blue) // ---------------------------------------------------------------- void Bmp::fillRectangle( int x, int y, int width, int height, unsigned char red, unsigned char green, unsigned char blue ) { for(int i = 0; i < width; i ++) { drawLine( x + i, y, x + i, y + height, red, green, blue ); } } // ---------------------------------------------------------------- // Fill a rectangle at (x, y) with the specified width and // height using color: theColor // ---------------------------------------------------------------- void Bmp::fillRectangle( int x, int y, int width, int height, const Types::Color &theColor ) { fillRectangle( x, y, width, height, theColor.mRed, theColor.mGreen, theColor.mBlue ); } // ---------------------------------------------------------------- // Fill a polygon using color: (red, green, blue), // expects 'points' to contain an even number of values // ---------------------------------------------------------------- void Bmp::fillPolygon( const vector &points, unsigned char red, unsigned char green, unsigned char blue ) { int xMin = 0; int yMin = 0; int xMax = 0; int yMax = 0; for(int i = 0, n = points.size(); i < n; i ++) { int x = points[i].mX; int y = points[i].mY; if(i == 0) { xMin = xMax = x; yMin = yMax = y; } else { if(x < xMin) xMin = x; else if(x > xMax) xMax = x; if(y < yMin) yMin = y; else if(y > yMax) yMax = y; } } typedef vector YValues; typedef vector Buffer; int width = (xMax - xMin) + 1; Buffer theBuffer; for(int i = 0; i < width; i ++) theBuffer.push_back(YValues()); for(int i = 1, n = points.size(); i < n; i ++) { int x0 = points[i - 1].mX; int y0 = points[i - 1].mY; int x1 = points[i].mX; int y1 = points[i].mY; if(x0 == x1) { theBuffer[x0 - xMin].push_back(y0); theBuffer[x0 - xMin].push_back(y1); } else { double delta = (y1 - y0) / (double)(x1 - x0); int start = 0; int stop = 0; double y = 0.0; if(x0 < x1) { y = y0; start = x0; stop = x1; } else { y = y1; start = x1; stop = x0; } for(int j = start; j < stop; j ++) { int yValue = (int)(y + 0.5); theBuffer[j - xMin].push_back(yValue); y += delta; } } } for(int i = 0, n = theBuffer.size(); i < n; i ++) sort(theBuffer[i].begin(), theBuffer[i].end()); for(int i = 0, n = theBuffer.size(); i < n; i ++) { for(int j = 1, nn = theBuffer[i].size(); j < nn; j += 2) { drawLine( xMin + i, theBuffer[i][j], xMin + i, theBuffer[i][j - 1], red, green, blue ); } } } // ---------------------------------------------------------------- // Fill a polygon using color: theColor // ---------------------------------------------------------------- void Bmp::fillPolygon(const vector &points, const Types::Color &theColor) { fillPolygon(points, theColor.mRed, theColor.mGreen, theColor.mBlue); } // ---------------------------------------------------------------- // Draw a line from (x0, y0) to (x1, y1) using // color: (red, green, blue) // ---------------------------------------------------------------- void Bmp::drawLine( int x0, int y0, int x1, int y1, unsigned char red, unsigned char green, unsigned char blue ) { int xDelta = (x1 - x0); int yDelta = (y1 - y0); if(xDelta == 0) { // A vertical line if(y0 > y1) std::swap(y0, y1); for(int y = y0; y <= y1; y ++) setPixel(x0, y, red, green, blue); } else if(yDelta == 0) { // A horizontal line if(x0 > x1) std::swap(x0, x1); for(int x = x0; x <= x1; x ++) setPixel(x, y0, red, green, blue); } else { setPixel(x0, y0, red, green, blue); int xStep = (xDelta < 0 ? -1 : 1); int yStep = (yDelta < 0 ? -1 : 1); xDelta = abs(xDelta) / 2; yDelta = abs(yDelta) / 2; if(xDelta >= yDelta) { int error = yDelta - 2 * xDelta; while(x0 != x1) { if(error >= 0 && (error || xStep > 0)) { error -= xDelta; y0 += yStep; } error += yDelta; x0 += xStep; setPixel(x0, y0, red, green, blue); } } else { int error = xDelta - 2 * yDelta; while(y0 != y1) { if(error >= 0 && (error || yStep > 0)) { error -= yDelta; x0 += xStep; } error += xDelta; y0 += yStep; setPixel(x0, y0, red, green, blue); } } } } // ---------------------------------------------------------------- // Draw a line from (x0, y0) to (x1, y1) using color: theColor // ---------------------------------------------------------------- void Bmp::drawLine( int x0, int y0, int x1, int y1, const Types::Color &theColor ) { drawLine( x0, y0, x1, y1, theColor.mRed, theColor.mGreen, theColor.mBlue ); } // ---------------------------------------------------------------- // Draw a polyline, given points: // // { x0, y0, x1, y1, x2, y2, ... } // // A line will be drawn from: (x0, y0) to (x1, y1), from (x1, y1) // to (x2, y2), etc. Color will be: (red, green, blue) // ---------------------------------------------------------------- void Bmp::drawPolyline( const vector &points, unsigned char red, unsigned char green, unsigned char blue ) { int xPrevious = 0; int yPrevious = 0; for(int i = 0, n = points.size(); i < n; i ++) { if(i == 0) { xPrevious = points[i].mX; yPrevious = points[i].mY; } else { int x = points[i].mX; int y = points[i].mY; drawLine(xPrevious, yPrevious, x, y, red, green, blue); xPrevious = x; yPrevious = y; } } } // ---------------------------------------------------------------- // draw polyline using the given points and color // ---------------------------------------------------------------- void Bmp::drawPolyline(const vector &points, const Types::Color &theColor) { drawPolyline(points, theColor.mRed, theColor.mGreen, theColor.mBlue); } // ---------------------------------------------------------------- // Draw circle at (xCenter, yCenter) using the specified color // and radius // ---------------------------------------------------------------- void Bmp::drawCircle( int xCenter, int yCenter, int radius, unsigned char red, unsigned char green, unsigned char blue ) { // ----------------------------------------------------------- // Draw a circle at (xCenter, yCenter) with radius 'radius' // using the specified color. // // (x-xCenter)^2 + (y-yCenter)^2 = radius^2 // // (y-yCenter)^2 = radius^2 - (x-xCenter)^2 // // (y-yCenter) = sqrt(radius^2 - (x-xCenter)^2) // // y = yCenter +/- sqrt(radius^2 - (x-xCenter)^2) // // ----------------------------------------------------------- int xLast = xCenter - radius; int y1Last = yCenter; int y2Last = yCenter; double r2 = radius * radius; for(double x = xLast; x <= xCenter + radius; x += 1.0) { double value = sqrt(r2 - (x - xCenter) * (x - xCenter)); int xCurrent = (int)(x + 0.5); int y1 = (int)(yCenter + value + 0.5); int y2 = (int)(yCenter - value + 0.5); drawLine(xCurrent, y1Last, xCurrent, y1, red, green, blue); drawLine(xCurrent, y2Last, xCurrent, y2, red, green, blue); xLast = xCurrent; y1Last = y1; y2Last = y2; } } // ---------------------------------------------------------------- // Draw circle at (xCenter, yCenter) using the specified color // and radius // ---------------------------------------------------------------- void Bmp::drawCircle( int xCenter, int yCenter, int radius, const Types::Color &color ) { drawCircle(xCenter, yCenter, radius, color.mRed, color.mGreen, color.mBlue); } // ---------------------------------------------------------------- // fill circle at (xCenter, yCenter) using the specified color // and radius // ---------------------------------------------------------------- void Bmp::fillCircle( int xCenter, int yCenter, int radius, unsigned char red, unsigned char green, unsigned char blue ) { double r2 = radius * radius; for(double x = xCenter - radius; x <= xCenter + radius; x += 1.0) { double value = sqrt(r2 - (x - xCenter) * (x - xCenter)); int xCurrent = (int)(x + 0.5); int y1 = (int)(yCenter + value + 0.5); int y2 = (int)(yCenter - value + 0.5); drawLine(xCurrent, y1, xCurrent, y2, red, green, blue); } } // ---------------------------------------------------------------- // fill circle at (xCenter, yCenter) using the specified color // and radius // ---------------------------------------------------------------- void Bmp::fillCircle( int xCenter, int yCenter, int radius, const Types::Color &color ) { fillCircle(xCenter, yCenter, radius, color.mRed, color.mGreen, color.mBlue); } // ---------------------------------------------------------------- // local function: getRowPadding, rows must have a length that's // a multiple of four // ---------------------------------------------------------------- static int getRowPadding(int width) { int rowSize = width * sizeof(Types::Color); int extra = 0; while((rowSize + extra) % 4 != 0) ++extra; return(extra); } // ---------------------------------------------------------------- // local function: doWrite, write 'theItem' to 'out' // ---------------------------------------------------------------- template static bool doWrite(std::ofstream &out, const T &theItem) { out.write((const char *)&theItem, sizeof(theItem)); return(!out.bad()); } // ---------------------------------------------------------------- // write the bmp to 'fileName', populates errMsg and returns false // on error, returns true on success // ---------------------------------------------------------------- bool Bmp::write(const string &fileName, string &errMsg) { std::ofstream out(fileName, std::ios::binary); if(!out) { errMsg = "Could not open: [" + fileName + "] for writing"; return(false); } // Header sizes ... const int BMP_FILE_HEADER_SIZE = 14; const int BMP_INFO_HEADER_SIZE = 40; if(!doWrite(out, 'B') || !doWrite(out, 'M')) { errMsg = "Error writing bmp header to: [" + fileName + "]"; return(false); } int fileSize = mWidth * mHeight * 3 + BMP_FILE_HEADER_SIZE + BMP_INFO_HEADER_SIZE; short reserved = 0; short colorPlanes = 1; short bitsPerPixel = 24; int offset = BMP_FILE_HEADER_SIZE + BMP_INFO_HEADER_SIZE; int headerSize = BMP_INFO_HEADER_SIZE; int zero = 0; if(!doWrite(out, fileSize), !doWrite(out, reserved), !doWrite(out, reserved), !doWrite(out, offset), !doWrite(out, headerSize), !doWrite(out, mWidth), !doWrite(out, mHeight), !doWrite(out, colorPlanes), !doWrite(out, bitsPerPixel), !doWrite(out, zero), !doWrite(out, zero), !doWrite(out, zero), !doWrite(out, zero), !doWrite(out, zero), !doWrite(out, zero)) { errMsg = "Error writing bmp header to: [" + fileName + "]"; return(false); } int rowPadding = getRowPadding(mWidth); for(int i = 0; i < mHeight; i ++) { for(int j = 0; j < mWidth; j ++) { // bmp colors are: blue, green, red instead of: red, green, blue if(!doWrite(out, mImage[i][j].mBlue) || !doWrite(out, mImage[i][j].mGreen) || !doWrite(out, mImage[i][j].mRed)) { errMsg = "Error writing image data to: [" + fileName + "]"; return(false); } } for(int j = 0; j < rowPadding; j ++) { if(!doWrite(out, '\0')) { errMsg = "Error writing row padding to: [" + fileName + "]"; return(false); } } } return(true); } // ---------------------------------------------------------------- // bitmap width // ---------------------------------------------------------------- int Bmp::getWidth() const { return(mWidth); } // ---------------------------------------------------------------- // bitmap height // ---------------------------------------------------------------- int Bmp::getHeight() const { return(mHeight); }