/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * 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 3 of the License, or * (at your option) 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 * aint32 with this program; if not, write to the Free Software * * * Based on the original sources * Faery Tale II -- The Halls of the Dead * (c) 1993-1996 The Wyrmkeep Entertainment Co. */ #include "saga2/saga2.h" #include "saga2/gdraw.h" #include "saga2/gblitter.h" namespace Saga2 { #define TempAlloc malloc #define TempFree free void gPort::setMap(gPixelMap *newmap, bool inverted) { _map = newmap; _clip = Rect16(0, 0, _map->_size.x, _map->_size.y); // Added by Talin to support inverted maps if (inverted) { _baseRow = _map->_data + _map->bytes() - _map->_size.x; _rowMod = -_map->_size.x; } else { _baseRow = _map->_data; _rowMod = _map->_size.x; } } /****** gdraw.cpp/gPort::setState ********************************* * * NAME * gPort::setState -- restore the state of a gPort * * SYNOPSIS * port.setState( state ); * * void gPort::setState( gPenState & ); * * FUNCTION * This function restores the state of a gPort from a gPenState * record. The record should have been filled in by an earlier * call to gPort::getState. These two functions are intended for * situations where it is necessary to save and restore the * drawing state of a gPort. * * INPUTS * state A reference to the saved state record. * * RESULT * none * * SEE ALSO * gPort class * gPort::getState * ********************************************************************** */ void gPort::setState(gPenState &state) { setColor(state.fgPen); setBgColor(state.bgPen); setOutlineColor(state.olPen); setShadowColor(state.shPen); setMode((DrawModes)state.drawMode); } /****** gdraw.cpp/gPort::getState ********************************* * * NAME * gPort::getState -- save the drawing state of a gPort * * SYNOPSIS * port.getState( state ); * * void gPort::getState( gPenState & ); * * FUNCTION * This function saves the "state" of a gPort into a small record. * The values saved are all of the pens and the drawing mode. * * When used in conjunction with gPort::setState, this allows you * to save and restore the state of a gPort. * * INPUTS * state A reference to the state record to be filled in. * * RESULT * none * * SEE ALSO * gPort class * gPort::setState * ********************************************************************** */ void gPort::getState(gPenState &state) { state.fgPen = _fgPen; state.bgPen = _bgPen; state.olPen = _olPen; state.shPen = _shPen; state.drawMode = _drawMode; } /****** gdraw.cpp/gPort::fillRect ********************************* * * NAME * gPort::fillRect -- fill a rectangle with a solid color. * * SYNOPSIS * port.fillRect( rect ); * port.fillRect( x, y, w, h ); * * void gPort::fillRect( const Rect16 ); * void gPort::fillRect( int16, int16, int16, int16 ); * * FUNCTION * Fill a rectangular area with a solid color. * kDrawModeComplement is supported; All other draw modes cause * a solid filling of pixels. * * INPUTS * x,y,w,h The coordinates of the rectangle to fill * * rect The rectangle to fill as a Rect16 * * RESULT * none * * NOTES * This function is currently in C and uses memset (for the gPort * version only -- the subclasses all have more optimized routines). * The base class rectfill should probably also be optimized. * * SEE ALSO * gPort class * ********************************************************************** */ // Your basic rectfill operation // REM: Probably better to recode this in assembly? void gPort::fillRect(const Rect16 r) { Rect16 sect; sect = intersect(_clip, r); // intersect with clip rect sect.x += _origin.x; // apply origin translate sect.y += _origin.y; if (!sect.empty()) { // if result is non-empty uint8 *addr = _baseRow + sect.y * _rowMod + sect.x; if (_drawMode == kDrawModeComplement) { // Complement drawing mode for (int h = sect.height; h > 0; h--, addr += _rowMod) { uint16 w = sect.width; uint8 *put = addr; while (w--) *put++ ^= _fgPen; } } else { _FillRect(addr, _rowMod, sect.width, sect.height, _fgPen); /* for (int h = sect.height; h > 0; h--, addr += _rowMod) { memset( addr, _fgPen, sect.width ); } */ } } } /****** gdraw.cpp/gPort::frameRect ******************************** * * NAME gPort::frameRect * * SYNOPSIS * port.frameRect( rect, thickness ); * port.frameRect( x, y, w, h, thickness ); * * void gPort::frameRect( const Rect16, int16 ); * void gPort::frameRect( int16, int16, int16, int16, int16 ); * * FUNCTION * This function draws a frame around a rectangle. The frame is * contained completely within the area of the rectangle. The * thickness parameter specifies how many pixel thick the frame * is -- The thickness increases towards the center of the rectangle. * * For example, if the rectangle has an X of 0 and a width of 100, * and the frame is 10 pixels thick, then the left edge of the frame * will span pixels 0-9 and the right edge pixels 90-99. * * INPUTS * x,y,w,h The coordinates of the rectangle to frame * * rect The rectangle to frame as a Rect16 * * thickness The thickness of the frame. * * RESULT * none * * SEE ALSO * gPort class * ********************************************************************** */ void gPort::frameRect(const Rect16 r, int16 thick) { int16 dThick = thick * 2; if (dThick >= r.width || dThick >= r.height) { fillRect(r); } else { fillRect(Rect16(r.x, r.y, r.width, thick)); fillRect(Rect16(r.x, r.y + r.height - thick, r.width, thick)); fillRect(Rect16(r.x, r.y + thick, thick, r.height - dThick)); fillRect(Rect16(r.x + r.width - thick, r.y + thick, thick, r.height - dThick)); } } /****** gdraw.cpp/gPort::hLine ************************************ * * NAME * gPort::hLine -- optimized rendering of horizontal lines * * SYNOPSIS * port.hLine( x, y, width ); * * void gPort::hLine( int16, int16, int16 ); * * FUNCTION * This function draws a 1-pixel-thick horizontal line starting * from the coordinates at (x,y) and extending for "width" pixels. * kDrawModeComplement is supported. * * Calling this function is faster that the general "line" routine. * It is used in many of the GTools bevel-drawing functions. * * INPUTS * x,y The left edge of the line * * width width of the line in pixels. * * RESULT * none * * NOTES * This function needs to be downcoded to assembly. * Note: The VGA and SVGA subclass versions of this function * are not as efficient (they just call RectFill). * * SEE ALSO * gPort class * ********************************************************************** */ void gPort::hLine(int16 x, int16 y, int16 width) { Rect16 sect; // Temporarily convert the coords into a rectangle, for // easy clipping sect = intersect(_clip, Rect16(x, y, width, 1)); sect.x += _origin.x; // apply origin translate sect.y += _origin.y; if (!sect.empty()) { // if result is non-empty if (_drawMode == kDrawModeComplement) { uint8 *addr = _baseRow + (y + _origin.y) * _rowMod + x + _origin.x; while (sect.width--) *addr++ ^= _fgPen; } else { _HLine(_baseRow + sect.y * _rowMod + sect.x, sect.width, _fgPen); /* memset( _baseRow + sect.y * _rowMod + sect.x, _fgPen, sect.width ); */ } } } /****** gdraw.cpp/gPort::vLine ************************************ * * NAME * gPort::vLine -- optimized rendering of vertical lines * * SYNOPSIS * port.vLine( x, y, height ); * * void gPort::vLine( int16, int16, int16 ); * * FUNCTION * This function draws a 1-pixel-thick vertical line starting * from the coordinates at (x,y) and extending downward for * "height" pixels. kDrawModeComplement is supported. * * Calling this function is faster that the general "line" routine. * It is used in many of the GTools bevel-drawing functions. * * INPUTS * x,y The left edge of the line * * height height of the line in pixels. * * RESULT * none * * NOTES * This function needs to be downcoded to assembly. * Note: The VGA and SVGA subclass versions of this function * are not as efficient (they just call RectFill). * * SEE ALSO * gPort class * ********************************************************************** */ void gPort::vLine(int16 x, int16 y, int16 height) { int16 bottom = y + height; uint8 *addr; // Just for laughs, we'll do the clipping a different way if (x < _clip.x || x >= _clip.x + _clip.width) return; if (y < _clip.y) y = _clip.y; if (bottom > _clip.y + _clip.height) bottom = _clip.y + _clip.height; // And now, draw the line if (_drawMode == kDrawModeComplement) { for (addr = _baseRow + (y + _origin.y) * _rowMod + x + _origin.x; y < bottom; y++) { *addr ^= _fgPen; addr += _rowMod; } } else { for (addr = _baseRow + (y + _origin.y) * _rowMod + x + _origin.x; y < bottom; y++) { *addr = _fgPen; addr += _rowMod; } } } /* ======================================================================= * gPort class (graphics drawing port) * ======================================================================= */ // Bresenham line-drawing functions /****** gdraw.cpp/gPort::line ************************************* * * NAME * gPort::line -- general line-drawing routine * * SYNOPSIS * port.line( x1, y1, x2, y2 ); * port.line( startPoint, destPoint ); * * void gPort::line( int16 x1, int16 y1, int16 x2, int16 y2 ); * void gPort::line( Point16 p, Point16 p ); * * FUNCTION * This is the general bresenham line-drawing function. It draws * from a source point to a destination point. The pen position * is not affected. Clipping and kDrawModeComplement are supported. * * INPUTS * (first form) * * x1, y1 The starting point of the line * * x2, y2 The ending point of the line * * (second form) * * startPoint The starting point of the line as a Point16 * * endPoint The ending point of the line as a Point16 * * RESULT * * NOTES * This function is still in C and not very efficient. The VGA and * SVGA versions are also fairly inefficient. * * Also, I have not yet worked out the math to properly determine * the error terms for clipped lines. This potentially could cause * "jaggies" in the lines to match poorly when lines drawn in two * adjacent clip rectangles meet. To fix this I interate the * algorithm even for clipped pixels, which is a waste... * * SEE ALSO * gPort class * gPort::move * gPort::moveTo * gPort::draw * gPort::drawTo * ********************************************************************** */ void gPort::line(int16 x1, int16 y1, int16 x2, int16 y2) { bool clipNeeded = false; int16 xAbs, yAbs, xMove, yMove, xDir, yDir, i; int16 errTerm; int16 clipRight = _clip.x + _clip.width, clipBottom = _clip.y + _clip.height; uint8 *addr; if (x1 > x2) { // drawing left if (x1 < _clip.x || x2 >= clipRight) return; if (x2 < _clip.x || x1 >= clipRight) clipNeeded = true; xDir = xMove = -1; // amount to adjust address xAbs = x1 - x2; // length of line } else { // drawing right if (x2 < _clip.x || x1 >= clipRight) return; if (x1 < _clip.x || x2 >= clipRight) clipNeeded = true; xDir = xMove = 1; // amount to adjust address xAbs = x2 - x1; // length of line } if (y1 > y2) { // drawing up if (y1 < _clip.y || y2 >= clipBottom) return; if (y2 < _clip.y || y1 >= clipBottom) clipNeeded = true; yDir = -1; yAbs = y1 - y2; yMove = -_rowMod; } else { // drawing down if (y2 < _clip.y || y1 >= clipBottom) return; if (y1 < _clip.y || y2 >= clipBottom) clipNeeded = true; yDir = 1; yAbs = y2 - y1; yMove = _rowMod; } addr = _baseRow + (y1 + _origin.y) * _rowMod + x1 + _origin.x; if (clipNeeded) { // clipping versions if (xAbs > yAbs) { errTerm = yAbs - (xAbs >> 1); for (i = xAbs + 1; i > 0; i--) { if (x1 >= _clip.x && x1 < clipRight && y1 >= _clip.y && y1 < clipBottom) { if (_drawMode == kDrawModeComplement) *addr ^= _fgPen; else *addr = _fgPen; } if (errTerm > 0) { y1 += yDir; addr += yMove; errTerm -= xAbs; } x1 += xDir; addr += xMove; errTerm += yAbs; } } else { errTerm = xAbs - (yAbs >> 1); for (i = yAbs + 1; i > 0; i--) { if (x1 >= _clip.x && x1 < clipRight && y1 >= _clip.y && y1 < clipBottom) { if (_drawMode == kDrawModeComplement) *addr ^= _fgPen; else *addr = _fgPen; } if (errTerm > 0) { x1 += xDir; addr += xMove; errTerm -= yAbs; } y1 += yDir; addr += yMove; errTerm += xAbs; } } } else { // non-clipping versions if (xAbs > yAbs) { errTerm = yAbs - (xAbs >> 1); for (i = xAbs + 1; i > 0; i--) { if (_drawMode == kDrawModeComplement) *addr ^= _fgPen; else *addr = _fgPen; if (errTerm > 0) { y1 += yDir; addr += yMove; errTerm -= xAbs; } x1 += xDir; addr += xMove; errTerm += yAbs; } } else { errTerm = xAbs - (yAbs >> 1); for (i = yAbs + 1; i > 0; i--) { if (_drawMode == kDrawModeComplement) *addr ^= _fgPen; else *addr = _fgPen; if (errTerm > 0) { x1 += xDir; addr += xMove; errTerm -= yAbs; } y1 += yDir; addr += yMove; errTerm += xAbs; } } } } /****** gdraw.cpp/gPort::bltPixels ******************************** * * NAME * gPort::bltPixels -- general blitting routine * * SYNOPSIS * port.bltPixels( sourceMap, srcX, srcY, dstX, dstY, width, height ); * * void gPort::bltPixels( gPixelMap &, int, int, int, int, int, int ); * * FUNCTION * This is it! The general blitting routine for gPorts. It obeys * clipping and the drawModes (see gPort::setMode for more details * on THAT). * * INPUTS * sourceMap A gPixelMap containing the source image to blit. * * srcX, srcY The coordinates of the source image to start * blitting from. * * dstX, dstY The * * RESULT * none * * NOTES * This is still in C. However, for C it's not bad. The subclass * versions (VGA and SVGA) are in highly optimized assembly, though. * * SEE ALSO * gPort class * ********************************************************************** */ void gPort::bltPixels( const gPixelMap &src, int src_x, int src_y, int dst_x, int dst_y, int width, int height) { Rect16 r = Rect16(dst_x, dst_y, width, height), sect; uint8 *src_line, *dst_line; sect = intersect(_clip, r); if (!sect.empty()) { // if result is non-empty src_x += sect.x - r.x; src_y += sect.y - r.y; src_line = src._data + src_y * src._size.x + src_x; dst_line = _baseRow + (sect.y + _origin.y) * _rowMod + sect.x + _origin.x; if (_drawMode == kDrawModeMatte) { // Matte drawing mode for (int h = sect.height; h > 0; h--, src_line += src._size.x, dst_line += _rowMod) { uint8 *src_ptr = src_line, *dst_ptr = dst_line; for (int w = sect.width; w > 0; w--) { if (*src_ptr) *dst_ptr++ = *src_ptr++; else dst_ptr++, src_ptr++; } } } else if (_drawMode == kDrawModeColor) { // Color drawing mode // Draws single color, except where for (int h = sect.height; // src pixels are transparent h > 0; h--, src_line += src._size.x, dst_line += _rowMod) { uint8 *src_ptr = src_line, *dst_ptr = dst_line; for (int w = sect.width; w > 0; w--) { if (*src_ptr++) *dst_ptr++ = _fgPen; else dst_ptr++; } } } else if (_drawMode == kDrawModeReplace) { // Replacement drawing mode for (int h = sect.height; h > 0; h--, src_line += src._size.x, dst_line += _rowMod) { memcpy(dst_line, src_line, sect.width); } } else if (_drawMode == kDrawModeComplement) { // Complement drawing mode // Inverts pixels, except where for (int h = sect.height; // src is transparent h > 0; h--, src_line += src._size.x, dst_line += _rowMod) { uint8 *src_ptr = src_line, *dst_ptr = dst_line; for (int w = sect.width; w > 0; w--) { if (*src_ptr++) *dst_ptr++ ^= _fgPen; else dst_ptr++; } } } } } /****** gdraw.cpp/gPort::bltPixelMask ******************************** * * NAME * gPort::bltPixelMask -- blit pixels through a mask * * SYNOPSIS * port.bltPixelMask( sourceMap, maskMap, srcX, srcY, * /c/ dstX, dstY, width, height ); * * void gPort::bltPixels( gPixelMap &, gPixelMap &, int, int, * /c/ int, int, int, int ); * * FUNCTION * This is similar to the bltPixels function, except it takes * an additional parameter, which specifies a "mask" bitmap. The mask * bitmap is assumed to be the same size and congruent to the * the source pixel map. * * The mask map affects the blit as follows: Any time a pixel within * the mask map is zero, the corresponding source pixel will not * be blitted. * * INPUTS * sourceMap A gPixelMap containing the source image to blit. * * maskMap A gPixelMap containing the image mask. * * srcX, srcY The coordinates of the source image to start * blitting from. * * dstX, dstY The * * RESULT * none * * NOTES * This is still in C. However, for C it's not bad. The subclass * versions (VGA and SVGA) are in highly optimized assembly, though. * * SEE ALSO * gPort class * ********************************************************************** */ void gPort::bltPixelMask( gPixelMap &src, gPixelMap &msk, int src_x, int src_y, int dst_x, int dst_y, int width, int height) { Rect16 r = Rect16(dst_x, dst_y, width, height), sect; uint8 *src_line, *dst_line, *msk_line; sect = intersect(_clip, r); if (!sect.empty()) { // if result is non-empty src_x += sect.x - r.x; src_y += sect.y - r.y; src_line = src._data + src_y * src._size.x + src_x; msk_line = msk._data + src_y * msk._size.x + src_x; dst_line = _baseRow + (sect.y + _origin.y) * _rowMod + sect.x + _origin.x; for (int h = sect.height; h > 0; h--, src_line += src._size.x, dst_line += _rowMod, msk_line += msk._size.x) { uint8 *src_ptr = src_line, *dst_ptr = dst_line, *msk_ptr = msk_line; for (int w = sect.width; w > 0; w--) { if (*msk_ptr++) *dst_ptr++ = *src_ptr++; else dst_ptr++, src_ptr++; } } } } /****** gdraw.cpp/gPort::scrollPixels ***************************** * * NAME * gPort::scrollPixels -- scroll pixels within a rectangle * * SYNOPSIS * port.scrollPixels( rect, dx, dy ); * * void gPort::scrollPixels( Rect16, int, int ); * * FUNCTION * This function scrolls the pixels within the rectangle "rect" * in the direction (dx, dy). Thus, if dx and dy are positive, the * image within the rectangle will appear to move to the lower right. * * The function does not attempt to clear or otherwise deal with * "newly revealed" pixels. * * The rectangle is intersected with the current clip region * before the operation proceeds. * * INPUTS * rect The rectangle to limit the scrolling to. * * dx,dy The direction of the scroll. * * RESULT * none * * NOTES * This function is NOT IMPLEMENTED for the gDisplayPorts (i.e. * the SVGA and VGA subclasses). The reason for this is the * difficulty of implementing this on all SVGA cards (For example, * if a card only has a single memory window, it would require an * extra off-screen buffer to insure that all possible scrollPixels * operations could succeed). * * SEE ALSO * gPort class * ********************************************************************** */ void gPort::scrollPixels( const Rect16 r, // area to scroll int dx, // amount to scroll by int dy) { Rect16 sect; uint8 *src_ptr, *dst_ptr; sect = intersect(_clip, r); if (dx == 0 && dy == 0) return; if (!sect.empty()) { // if result is non-empty uint16 w = sect.width, h = sect.height, mod = _rowMod; Point16 src(sect.x + _origin.x, sect.y + _origin.y), dst(sect.x + _origin.x, sect.y + _origin.y); if (dx > 0) { dst.x += dx; w -= dx; } else { src.x -= dx; w += dx; } if (dy > 0) { dst.y += dy; h -= dy; } else { src.y -= dy; h += dy; } if (w <= 0 || h <= 0) return; if (src.y > dst.y || (src.y == dst.y && src.x > dst.x)) { src_ptr = _baseRow + src.y * mod + src.x; dst_ptr = _baseRow + dst.y * mod + dst.x; mod -= w; while (h--) { for (int w1 = w; w1 > 0; w1--) *dst_ptr++ = *src_ptr++; src_ptr += mod; dst_ptr += mod; } } else { src_ptr = _baseRow + (src.y + h - 1) * mod + src.x + w; dst_ptr = _baseRow + (dst.y + h - 1) * mod + dst.x + w; mod -= w; while (h--) { for (int w1 = w; w1 > 0; w1--) *--dst_ptr = *--src_ptr; src_ptr -= mod; dst_ptr -= mod; } } } } /* ======================================================================= * Image Mapping * ======================================================================= */ /****** gdraw.cpp/mapImage **************************************** * * NAME * mapImage -- translate an image via a translation table * * SYNOPSIS * mapImage( sourceMap, destMap, translation ); * void mapImage( sourePort, destPort, translation ); * * void mapImage( gPixelMap &, gPixelMap &, gPen map[] ) * void mapImage( gPort, gPort, gPen map[] ) * * FUNCTION * Runs an image through a color translation. Right now it assumes * that the source image has exactly the same dimensions as the * destination image. * * INPUTS * sourceMap The map to translate * * destMap Where to put the translated pixels * * translation The lookup table to translate the pixels through. * * (second form, which takes gPorts instead of gPixelMaps) * * sourcePort The source pixels (in a gPort) * * destPort The destination pixels (in a gPort) * * RESULT * * NOTES * This could easily be downcoded. * ********************************************************************** */ void mapImage(gPixelMap &from, gPixelMap &to, gPen map[]) { int32 bytes = to.bytes(); uint8 *get = from._data, *put = to._data; while (bytes--) *put++ = map[*get++]; } void mapImage(gPort &from, gPort &to, gPen map[]) { mapImage(*from._map, *to._map, map); } /* ======================================================================= * Temporary images * ======================================================================= */ /****** gdraw.cpp/NewTempPort ************************************* * * NAME * NewTempPort -- create a new temporary gPort * * SYNOPSIS * error = NewTempPort( port, width, height ); * * errorCode NewTempPort( gPort &, int, int ); * * FUNCTION * This function takes a raw, uninitialized gPort, creates * a temporary gPixelMap, and attaches it to the gPort. It's * mainly used internally by gTools when it needs somewhere to * temporarily stash an image. * * INPUTS * port The port object to attach the temporary pixel map to. * * width,height The size of the pixel map to be created. * * RESULT * If it succeed, the error will be "errOK". * * NOTES * We should phase out the old method of handling errors and * use the new exception-based error handling. * * SEE ALSO * gPort class * gPixelMap class * DisposeTempPort * ********************************************************************** */ bool NewTempPort(gPort &port, int width, int height) { gPixelMap *map; map = (gPixelMap *)TempAlloc(width * height + sizeof(gPixelMap)); if (map != nullptr) { map->_data = (uint8 *)(map + 1); map->_size.x = width; map->_size.y = height; port.setMap(map); return true; } else return false; } /****** gdraw.cpp/DisposeTempPort ********************************* * * NAME * DisposeTempPort -- disposes of temporary pixel map * * SYNOPSIS * DisposeTempPort( port ); * * void DisposeTempPort( gPort & ); * * FUNCTION * This function disposes of the temporary pixel map which * was created by NewTempPort. * * INPUTS * port The same port that was passed to NewTempPort * * RESULT * none * * SEE ALSO * gPort class * gPixelMap class * NewTempPort * ********************************************************************** */ void DisposeTempPort(gPort &port) { if (port._map) TempFree(port._map); port._map = nullptr; } } // end of namespace Saga2