/* * The Mana Client * Copyright (C) 2004-2009 The Mana World Development Team * Copyright (C) 2009-2012 The Mana Developers * * This file is part of The Mana Client. * * This program 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 2 of the License, or * any later version. * * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */ #include "graphics.h" #include "log.h" #include "resources/image.h" #include "utils/gettext.h" #include <guichan/exception.hpp> void ImageRect::setAlpha(float alpha) { for (auto img : grid) { img->setAlpha(alpha); } } Graphics::~Graphics() { _endDraw(); } void Graphics::setTarget(SDL_Window *target) { _endDraw(); mTarget = target; if (mTarget) _beginDraw(); } bool Graphics::setVideoMode(int w, int h, bool fs) { logger->log("Setting video mode %dx%d %s", w, h, fs ? "fullscreen" : "windowed"); int windowFlags = SDL_WINDOW_ALLOW_HIGHDPI; if (fs) windowFlags |= SDL_WINDOW_FULLSCREEN; else windowFlags |= SDL_WINDOW_RESIZABLE; // TODO_SDL2: Support SDL_WINDOW_FULLSCREEN_DESKTOP SDL_Window *window = nullptr; SDL_Renderer *renderer = nullptr; SDL_CreateWindowAndRenderer(w, h, windowFlags, &window, &renderer); if (!window) return false; SDL_SetWindowMinimumSize(window, 640, 480); SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND); setTarget(window); mRenderer = renderer; mWidth = w; mHeight = h; mFullscreen = fs; if (const char *driver = SDL_GetCurrentVideoDriver()) logger->log("Using video driver: %s", driver); else logger->log("Using video driver: not initialized"); SDL_RendererInfo info; if (SDL_GetRendererInfo(mRenderer, &info) == 0) { logger->log("Using renderer: %s", info.name); logger->log("The renderer is a software fallback: %s", (info.flags & SDL_RENDERER_SOFTWARE) ? "yes" : "no"); logger->log("The renderer is hardware accelerated: %s", (info.flags & SDL_RENDERER_ACCELERATED) ? "yes" : "no"); logger->log("Vsync: %s", (info.flags & SDL_RENDERER_PRESENTVSYNC) ? "on" : "off"); logger->log("Renderer supports rendering to texture: %s", (info.flags & SDL_RENDERER_TARGETTEXTURE) ? "yes" : "no"); logger->log("Max texture size: %dx%d", info.max_texture_width, info.max_texture_height); } return true; } bool Graphics::changeVideoMode(int w, int h, bool fs) { // Just return success if we're already in this mode if (mWidth == w && mHeight == h && mFullscreen == fs) return true; _endDraw(); bool success = setVideoMode(w, h, fs); // If it didn't work, try to restore the previous mode. If that doesn't // work either, we're in big trouble and bail out. if (!success) { if (!setVideoMode(mWidth, mHeight, mFullscreen)) { logger->error(_("Failed to change video mode and couldn't " "switch back to the previous mode!")); } } _beginDraw(); return success; } void Graphics::videoResized(int w, int h) { mWidth = w; mHeight = h; } int Graphics::getWidth() const { return mWidth; } int Graphics::getHeight() const { return mHeight; } bool Graphics::drawImage(Image *image, int x, int y) { if (!image) return false; return drawImage(image, 0, 0, x, y, image->mBounds.w, image->mBounds.h); } bool Graphics::drawRescaledImage(Image *image, int srcX, int srcY, int dstX, int dstY, int width, int height, int desiredWidth, int desiredHeight, bool useColor) { // Check that preconditions for blitting are met. if (!mTarget || !image || !image->mTexture) return false; dstX += mClipStack.top().xOffset; dstY += mClipStack.top().yOffset; srcX += image->mBounds.x; srcY += image->mBounds.y; SDL_Rect dstRect; SDL_Rect srcRect; dstRect.x = dstX; dstRect.y = dstY; srcRect.x = srcX; srcRect.y = srcY; srcRect.w = width; srcRect.h = height; dstRect.w = desiredWidth; dstRect.h = desiredHeight; return !(SDL_RenderCopy(mRenderer, image->mTexture, &srcRect, &dstRect) < 0); } bool Graphics::drawImage(Image *image, int srcX, int srcY, int dstX, int dstY, int width, int height, bool useColor) { if (!image) return false; return drawRescaledImage(image, srcX, srcY, dstX, dstY, width, height, width, height, useColor); } void Graphics::drawImagePattern(Image *image, int x, int y, int w, int h) { // Check that preconditions for blitting are met. if (!image) return; const int iw = image->getWidth(); const int ih = image->getHeight(); drawRescaledImagePattern(image, x, y, w, h, iw, ih); } void Graphics::drawRescaledImagePattern(Image *image, int x, int y, int w, int h, int scaledWidth, int scaledHeight) { // Check that preconditions for blitting are met. if (!mTarget || !image || !image->mTexture) return; if (scaledHeight == 0 || scaledWidth == 0) return; for (int py = 0; py < h; py += scaledHeight) // Y position on pattern plane { int dh = (py + scaledHeight >= h) ? h - py : scaledHeight; int srcY = image->mBounds.y; int dstY = y + py + mClipStack.top().yOffset; for (int px = 0; px < w; px += scaledWidth) // X position on pattern plane { int dw = (px + scaledWidth >= w) ? w - px : scaledWidth; int srcX = image->mBounds.x; int dstX = x + px + mClipStack.top().xOffset; SDL_Rect dstRect; SDL_Rect srcRect; dstRect.x = dstX; dstRect.y = dstY; dstRect.w = dw; dstRect.h = dh; srcRect.x = srcX; srcRect.y = srcY; srcRect.w = dw; srcRect.h = dh; SDL_RenderCopy(mRenderer, image->mTexture, &srcRect, &dstRect); } } } void Graphics::drawImageRect(int x, int y, int w, int h, Image *topLeft, Image *topRight, Image *bottomLeft, Image *bottomRight, Image *top, Image *right, Image *bottom, Image *left, Image *center) { pushClipArea(gcn::Rectangle(x, y, w, h)); // Draw the center area drawImagePattern(center, topLeft->getWidth(), topLeft->getHeight(), w - topLeft->getWidth() - topRight->getWidth(), h - topLeft->getHeight() - bottomLeft->getHeight()); // Draw the sides drawImagePattern(top, left->getWidth(), 0, w - left->getWidth() - right->getWidth(), top->getHeight()); drawImagePattern(bottom, left->getWidth(), h - bottom->getHeight(), w - left->getWidth() - right->getWidth(), bottom->getHeight()); drawImagePattern(left, 0, top->getHeight(), left->getWidth(), h - top->getHeight() - bottom->getHeight()); drawImagePattern(right, w - right->getWidth(), top->getHeight(), right->getWidth(), h - top->getHeight() - bottom->getHeight()); // Draw the corners drawImage(topLeft, 0, 0); drawImage(topRight, w - topRight->getWidth(), 0); drawImage(bottomLeft, 0, h - bottomLeft->getHeight()); drawImage(bottomRight, w - bottomRight->getWidth(), h - bottomRight->getHeight()); popClipArea(); } void Graphics::drawImageRect(int x, int y, int w, int h, const ImageRect &imgRect) { drawImageRect(x, y, w, h, imgRect.grid[0], imgRect.grid[2], imgRect.grid[6], imgRect.grid[8], imgRect.grid[1], imgRect.grid[5], imgRect.grid[7], imgRect.grid[3], imgRect.grid[4]); } void Graphics::updateScreen() { SDL_RenderPresent(mRenderer); } SDL_Surface *Graphics::getScreenshot() { #if SDL_BYTEORDER == SDL_BIG_ENDIAN int rmask = 0xff000000; int gmask = 0x00ff0000; int bmask = 0x0000ff00; #else int rmask = 0x000000ff; int gmask = 0x0000ff00; int bmask = 0x00ff0000; #endif int amask = 0x00000000; SDL_Surface *screenshot = SDL_CreateRGBSurface(0, mWidth, mHeight, 24, rmask, gmask, bmask, amask); SDL_RenderReadPixels(mRenderer, nullptr, screenshot->format->format, screenshot->pixels, screenshot->pitch); return screenshot; } bool Graphics::pushClipArea(gcn::Rectangle area) { bool result = gcn::Graphics::pushClipArea(area); updateSDLClipRect(); return result; } void Graphics::popClipArea() { gcn::Graphics::popClipArea(); updateSDLClipRect(); } void Graphics::updateSDLClipRect() { if (mClipStack.empty()) { SDL_RenderSetClipRect(mRenderer, nullptr); return; } const gcn::ClipRectangle &carea = mClipStack.top(); SDL_Rect rect; rect.x = carea.x; rect.y = carea.y; rect.w = carea.width; rect.h = carea.height; SDL_RenderSetClipRect(mRenderer, &rect); } void Graphics::drawPoint(int x, int y) { if (mClipStack.empty()) { throw GCN_EXCEPTION("Clip stack is empty, perhaps you called a draw funtion outside of _beginDraw() and _endDraw()?"); } const gcn::ClipRectangle &top = mClipStack.top(); x += top.xOffset; y += top.yOffset; if (!top.isPointInRect(x, y)) return; SDL_SetRenderDrawColor(mRenderer, (Uint8)(mColor.r), (Uint8)(mColor.g), (Uint8)(mColor.b), (Uint8)(mColor.a)); SDL_RenderDrawPoint(mRenderer, x, y); } void Graphics::drawLine(int x1, int y1, int x2, int y2) { if (mClipStack.empty()) { throw GCN_EXCEPTION("Clip stack is empty, perhaps you called a draw funtion outside of _beginDraw() and _endDraw()?"); } const gcn::ClipRectangle &top = mClipStack.top(); x1 += top.xOffset; y1 += top.yOffset; x2 += top.xOffset; y2 += top.yOffset; SDL_SetRenderDrawColor(mRenderer, (Uint8)(mColor.r), (Uint8)(mColor.g), (Uint8)(mColor.b), (Uint8)(mColor.a)); SDL_RenderDrawLine(mRenderer, x1, y1, x2, y2); } void Graphics::drawRectangle(const gcn::Rectangle &rectangle) { if (mClipStack.empty()) { throw GCN_EXCEPTION("Clip stack is empty, perhaps you called a draw funtion outside of _beginDraw() and _endDraw()?"); } const gcn::ClipRectangle &top = mClipStack.top(); SDL_Rect rect; rect.x = rectangle.x + top.xOffset; rect.y = rectangle.y + top.yOffset; rect.w = rectangle.width; rect.h = rectangle.height; SDL_SetRenderDrawColor(mRenderer, (Uint8)(mColor.r), (Uint8)(mColor.g), (Uint8)(mColor.b), (Uint8)(mColor.a)); SDL_RenderDrawRect(mRenderer, &rect); } void Graphics::fillRectangle(const gcn::Rectangle &rectangle) { if (mClipStack.empty()) { throw GCN_EXCEPTION("Clip stack is empty, perhaps you called a draw funtion outside of _beginDraw() and _endDraw()?"); } const gcn::ClipRectangle &top = mClipStack.top(); gcn::Rectangle area = rectangle; area.x += top.xOffset; area.y += top.yOffset; if(!area.isIntersecting(top)) { return; } SDL_Rect rect; rect.x = area.x; rect.y = area.y; rect.w = area.width; rect.h = area.height; SDL_SetRenderDrawColor(mRenderer, (Uint8)(mColor.r), (Uint8)(mColor.g), (Uint8)(mColor.b), (Uint8)(mColor.a)); SDL_RenderFillRect(mRenderer, &rect); }