/* * Copyright 2010 OpenXcom Developers.. * * This file is part of OpenXcom. * * OpenXcom is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * OpenXcom is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenXcom. If not, see . */ #include "Surface.h" #include "SDL_gfxPrimitives.h" #include "Palette.h" #include namespace OpenXcom { /** * Sets up a blank 8bpp surface with the specified size and position, * with pure black as the transparent color. * @note Surfaces don't have to fill the whole size since their * background is transparent, specially subclasses with their own * drawing logic, so it just covers the maximum drawing area. * @param width Width in pixels. * @param height Height in pixels. * @param x X position in pixels. * @param y Y position in pixels. */ Surface::Surface(int width, int height, int x, int y) : _x(x), _y(y), _visible(true), _hidden(false) { _surface = SDL_CreateRGBSurface(SDL_SWSURFACE, width, height, 8, 0, 0, 0, 0); if (_surface == 0) { throw SDL_GetError(); } SDL_SetColorKey(_surface, SDL_SRCCOLORKEY, 0); _crop.w = 0; _crop.h = 0; _crop.x = 0; _crop.y = 0; } /** * Performs a deep copy of an existing surface. * @param other Surface to copy from. */ Surface::Surface(const Surface& other) { _x = other.getX(); _y = other.getY(); _crop.w = other._crop.w; _crop.h = other._crop.h; _crop.x = other._crop.x; _crop.y = other._crop.y; _visible = other._visible; _hidden = other._hidden; _surface = SDL_DisplayFormat(other._surface); } /** * Deletes the surface from memory. */ Surface::~Surface() { SDL_FreeSurface(_surface); } /** * Loads the contents of an X-Com SCR image file into * the surface. SCR files are simply uncompressed images * containing the palette offset of each pixel. * @param filename Filename of the SCR image. * @sa http://www.ufopaedia.org/index.php?title=Image_Formats#SCR_.26_DAT */ void Surface::loadScr(const std::string &filename) { // Load file and put pixels in surface std::ifstream imgFile (filename.c_str(), std::ios::in | std::ios::binary); if (!imgFile) { throw "Failed to load SCR"; } // Lock the surface lock(); char value; int x = 0, y = 0; while (imgFile.read(&value, 1)) { setPixelIterative(&x, &y, Uint8(value)); } if (!imgFile.eof()) { throw "Invalid data from file"; } // Unlock the surface unlock(); imgFile.close(); } /** * Loads the contents of an X-Com SPK image file into * the surface. SPK files are compressed with a custom * algorithm since they're usually full-screen images. * @param filename Filename of the SPK image. * @sa http://www.ufopaedia.org/index.php?title=Image_Formats#SPK */ void Surface::loadSpk(const std::string &filename) { // Load file and put pixels in surface std::ifstream imgFile (filename.c_str(), std::ios::in | std::ios::binary); if (!imgFile) { throw "Failed to load SPK"; } // Lock the surface lock(); Uint16 flag; char value; int x = 0, y = 0; while (imgFile.read((char*)&flag, sizeof(flag))) { if (flag == 65535) { imgFile.read((char*)&flag, sizeof(flag)); for (int i = 0; i < flag*2; i++) { setPixelIterative(&x, &y, 0); } } else if (flag == 65534) { imgFile.read((char*)&flag, sizeof(flag)); for (int i = 0; i < flag*2; i++) { imgFile.read(&value, 1); setPixelIterative(&x, &y, Uint8(value)); } } else if (flag == 65533) { break; } } /* if (!imgFile.eof()) { throw "Invalid data from file"; } */ // Unlock the surface unlock(); imgFile.close(); } /** * Clears the entire contents of the surface, resulting * in a blank image. */ void Surface::clear() { SDL_Rect square; square.x = 0; square.y = 0; square.w = getWidth(); square.h = getHeight(); SDL_FillRect(_surface, &square, 0); } /** * Shifts all the colors in the surface by a set amount. * This is a common method in 8bpp games to simulate color * effects for cheap. * @param off Amount to shift. * @param min Minimum color to shift to. * @param max Maximum color to shift to. */ void Surface::offset(int off, int min, int max) { // Lock the surface lock(); for (int x = 0, y = 0; x < getWidth() && y < getHeight();) { Uint8 pixel = getPixel(x, y); int p = pixel + off; if (min != -1 && p < min) { p = min; } else if (max != -1 && p > max) { p = max; } if (pixel > 0) { setPixelIterative(&x, &y, p); } else { setPixelIterative(&x, &y, 0); } } // Unlock the surface unlock(); } /** * Inverts all the colors in the surface according to a middle point. * Used for effects like shifting a button between pressed and unpressed. * @param mid Middle point. */ void Surface::invert(Uint8 mid) { // Lock the surface lock(); for (int x = 0, y = 0; x < getWidth() && y < getHeight();) { Uint8 pixel = getPixel(x, y); if (pixel > 0) { setPixelIterative(&x, &y, pixel + 2 * (mid - pixel)); } else { setPixelIterative(&x, &y, 0); } } // Unlock the surface unlock(); } /** * Sets the shade level of the surface. * Shade 0 is original color, 16 is black. * @param shade Amount to shade. */ void Surface::setShade(int shade) { // Lock the surface lock(); for (int x = 0, y = 0; x < _surface->w && y < _surface->h;) { Uint8 pixel = getPixel(x, y); if (pixel) { int baseColor = pixel/16; int originalShade = pixel%16; int newShade = originalShade + shade; if (newShade > 15) { baseColor = 0; newShade = 15; } if (originalShade != newShade || baseColor == 0) { pixel = (baseColor*16) + newShade; setPixel(x, y, pixel); } } x++; if (x == getWidth()) { y++; x = 0; } } // Unlock the surface unlock(); } /** * Runs any code the surface needs to keep updating every * game cycle, like animations and other real-time elements. */ void Surface::think() { } /** * Draws the graphic that the surface contains and * gets blitted onto other surfaces. */ void Surface::draw() { } /** * Blits this surface onto another one, with its position * relative to the top-left corner of the target surface. * The cropping rectangle controls the portion of the surface * that is blitted. * @param surface Pointer to surface to blit onto. */ void Surface::blit(Surface *surface) { if (_visible && !_hidden) { SDL_Rect* cropper; SDL_Rect target; if (_crop.w == 0 && _crop.h == 0) { cropper = 0; } else { cropper = &_crop; } target.x = getX(); target.y = getY(); SDL_BlitSurface(_surface, cropper, surface->getSurface(), &target); } } /** * Copies the exact contents of another surface onto this one. * Only the content that would overlap both surfaces is copied, in * accordance with their positions. This is handy for applying * effects over another surface without modifying the original. * @param surface Pointer to surface to copy from. */ void Surface::copy(Surface *surface) { SDL_Rect from; from.x = getX() - surface->getX(); from.y = getY() - surface->getY(); from.w = getWidth(); from.h = getHeight(); SDL_BlitSurface(surface->getSurface(), &from, _surface, 0); } /** * Copies the exact contents of another surface onto the areas that * match a certain color, like a mask. Surface sizes must match. * @param surface Pointer to surface to copy from. * @param mask Color mask to replace with the other surface. */ void Surface::maskedCopy(Surface *surface, Uint8 mask) { if (surface->getWidth() != getWidth() || surface->getHeight() != getHeight()) { return; } // Lock the surface lock(); for (int x = 0, y = 0; x < getWidth() && y < getHeight();) { if (getPixel(x, y) == mask) { setPixelIterative(&x, &y, surface->getPixel(x, y)); } else { setPixelIterative(&x, &y, this->getPixel(x, y)); } } // Unlock the surface unlock(); } /** * Draws a filled rectangle on the surface. * @param rect Pointer to Rect. * @param color Color of the rectangle. */ void Surface::drawRect(SDL_Rect *rect, Uint8 color) { SDL_FillRect(_surface, rect, color); } /** * Draws a line on the surface. * @param x1 Start x coordinate in pixels. * @param y1 Start y coordinate in pixels. * @param x2 End x coordinate in pixels. * @param y2 End y coordinate in pixels. * @param color Color of the line. */ void Surface::drawLine(Sint16 x1, Sint16 y1, Sint16 x2, Sint16 y2, Uint8 color) { lineColor(_surface, x1, y1, x2, y2, Palette::getRGBA(getPalette(), color)); } /** * Draws a filled circle on the surface. * @param x X coordinate in pixels. * @param y Y coordinate in pixels. * @param r Radius in pixels. * @param color Color of the circle. */ void Surface::drawCircle(Sint16 x, Sint16 y, Sint16 r, Uint8 color) { filledCircleColor(_surface, x, y, r, Palette::getRGBA(getPalette(), color)); } /** * Draws a filled polygon on the surface. * @param x Array of x coordinates. * @param y Array of y coordinates. * @param n Number of points. * @param color Color of the polygon. */ void Surface::drawPolygon(Sint16 *x, Sint16 *y, int n, Uint8 color) { filledPolygonColor(_surface, x, y, n, Palette::getRGBA(getPalette(), color)); } /** * Draws a textured polygon on the surface. * @param x Array of x coordinates. * @param y Array of y coordinates. * @param n Number of points. * @param texture Texture for polygon. * @param dx X offset of texture relative to the screen. * @param dy Y offset of texture relative to the screen. */ void Surface::drawTexturedPolygon(Sint16 *x, Sint16 *y, int n, Surface *texture, int dx, int dy) { texturedPolygon(_surface, x, y, n, texture->getSurface(), dx, dy); } /** * Draws a text string on the surface. * @param x X coordinate in pixels. * @param y Y coordinate in pixels. * @param s Character string to draw. * @param color Color of string. */ void Surface::drawString(Sint16 x, Sint16 y, const char *s, Uint8 color) { stringColor(_surface, x, y, s, Palette::getRGBA(getPalette(), color)); } /** * Changes the position of the surface in the X axis. * @param x X position in pixels. */ void Surface::setX(int x) { _x = x; } /** * Returns the position of the surface in the X axis. * @return X position in pixels. */ int Surface::getX() const { return _x; } /** * Changes the position of the surface in the Y axis. * @param y Y position in pixels. */ void Surface::setY(int y) { _y = y; } /** * Returns the position of the surface in the Y axis. * @return Y position in pixels. */ int Surface::getY() const { return _y; } /** * Resets the cropping rectangle set for this surface, * so the whole surface is blitted. */ void Surface::resetCrop() { _crop.w = 0; _crop.h = 0; _crop.x = 0; _crop.y = 0; } /** * Returns the cropping rectangle for this surface. * @return Pointer to the cropping rectangle. */ SDL_Rect *Surface::getCrop() { return &_crop; } /** * Replaces a certain amount of colors in the surface's palette. * @param colors Pointer to the set of colors. * @param firstcolor Offset of the first color to replace. * @param ncolors Amount of colors to replace. */ void Surface::setPalette(SDL_Color *colors, int firstcolor, int ncolors) { SDL_SetColors(_surface, colors, firstcolor, ncolors); } /** * Returns the surface's 8bpp palette. * @return Pointer to the palette's colors. */ SDL_Color *const Surface::getPalette() const { return _surface->format->palette->colors; } /** * Changes the color of a pixel in the surface, relative to * the top-left corner of the surface. * @param x X position of the pixel. * @param y Y position of the pixel. * @param pixel New color for the pixel. */ void Surface::setPixel(int x, int y, Uint8 pixel) { if (x < 0 || x >= getWidth() || y < 0 || y >= getHeight()) { return; } ((Uint8 *)_surface->pixels)[y * _surface->pitch + x * _surface->format->BytesPerPixel] = pixel; } /** * Changes the color of a pixel in the surface and returns the * next pixel position. Useful when changing a lot of pixels in * a row, eg. loading images. * @param x Pointer to the X position of the pixel. Changed to the next X position in the sequence. * @param y Pointer to the Y position of the pixel. Changed to the next Y position in the sequence. * @param pixel New color for the pixel. */ void Surface::setPixelIterative(int *x, int *y, Uint8 pixel) { setPixel(*x, *y, pixel); (*x)++; if (*x == getWidth()) { (*y)++; *x = 0; } } /** * Returns the color of a specified pixel in the surface. * @param x X position of the pixel. * @param y Y position of the pixel. * @return Color of the pixel. */ Uint8 Surface::getPixel(int x, int y) const { return ((Uint8 *)_surface->pixels)[y * _surface->pitch + x * _surface->format->BytesPerPixel]; } /** * Returns the internal SDL_Surface for SDL calls. * @return Pointer to the surface. */ SDL_Surface *const Surface::getSurface() const { return _surface; } /** * Returns the width of the surface. * @return Width in pixels. */ int Surface::getWidth() const { return _surface->w; } /** * Returns the height of the surface. * @return Height in pixels */ int Surface::getHeight() const { return _surface->h; } /** * Changes the visibility of the surface. A hidden surface * isn't blitted nor receives events. * @param visible New visibility. */ void Surface::setVisible(bool visible) { _visible = visible; } /** * Returns the visible state of the surface. * @return Current visibility. */ bool Surface::getVisible() const { return _visible; } /** * Hides the surface if it's visible. This is a separate * visibility setting intended for temporary effects, * so as to not override the actual visibility setting. */ void Surface::hide() { _hidden = true; } /** * Shows the surface if it's not invisible. This is a separate * visibility setting intended for temporary effects, * so as to not override the actual visibility setting. */ void Surface::show() { _hidden = false; } /** * Locks the surface from outside access * for pixel-level access. Must be unlocked * afterwards. * @sa unlock() */ void Surface::lock() { SDL_LockSurface(_surface); } /** * Unlocks the surface after it's been locked * to resume blitting operations. * @sa lock() */ void Surface::unlock() { SDL_UnlockSurface(_surface); } }