diff options
author | Paul Gilbert | 2013-11-30 20:44:23 -0500 |
---|---|---|
committer | Paul Gilbert | 2013-11-30 20:44:23 -0500 |
commit | ede418b67a0f14e4f17a2b03f5362741badd5532 (patch) | |
tree | 07de039fac5c303f1b9fce372afe5fa19854f547 /graphics | |
parent | 66d1f7a8de2ff5a21ad013f45924c406f4833e9a (diff) | |
parent | 3e859768770a0b385e21c4528cd546b33ed9a55d (diff) | |
download | scummvm-rg350-ede418b67a0f14e4f17a2b03f5362741badd5532.tar.gz scummvm-rg350-ede418b67a0f14e4f17a2b03f5362741badd5532.tar.bz2 scummvm-rg350-ede418b67a0f14e4f17a2b03f5362741badd5532.zip |
VOYEUR: Merge of upstream
Diffstat (limited to 'graphics')
30 files changed, 1545 insertions, 1334 deletions
diff --git a/graphics/VectorRenderer.h b/graphics/VectorRenderer.h index 0467cac946..8e1c5e91e1 100644 --- a/graphics/VectorRenderer.h +++ b/graphics/VectorRenderer.h @@ -73,6 +73,8 @@ struct DrawStep { uint8 shadow, stroke, factor, radius, bevel; /**< Misc options... */ uint8 fillMode; /**< active fill mode */ + uint8 shadowFillMode; /**< fill mode of the shadow used */ + uint32 extraData; /**< Generic parameter for extra options (orientation/bevel) */ uint32 scale; /**< scale of all the coordinates in FIXED POINT with 16 bits mantissa */ @@ -103,7 +105,7 @@ VectorRenderer *createRenderer(int mode); */ class VectorRenderer { public: - VectorRenderer() : _activeSurface(NULL), _fillMode(kFillDisabled), _shadowOffset(0), + VectorRenderer() : _activeSurface(NULL), _fillMode(kFillDisabled), _shadowOffset(0), _shadowFillMode(kShadowExponential), _disableShadows(false), _strokeWidth(1), _gradientFactor(1) { } @@ -126,6 +128,11 @@ public: kTriangleRight }; + enum ShadowFillMode { + kShadowLinear = 0, + kShadowExponential = 1 + }; + /** * Draws a line by considering the special cases for optimization. * @@ -278,7 +285,7 @@ public: * Clears the active surface. */ virtual void clearSurface() { - byte *src = (byte *)_activeSurface->pixels; + byte *src = (byte *)_activeSurface->getPixels(); memset(src, 0, _activeSurface->pitch * _activeSurface->h); } @@ -292,6 +299,10 @@ public: _fillMode = mode; } + virtual void setShadowFillMode(ShadowFillMode mode) { + _shadowFillMode = mode; + } + /** * Sets the stroke width. All shapes drawn with a stroke will * have that width. Pass 0 to disable shape stroking. @@ -466,7 +477,7 @@ public: */ virtual void drawString(const Graphics::Font *font, const Common::String &text, const Common::Rect &area, Graphics::TextAlign alignH, - GUI::ThemeEngine::TextAlignVertical alignV, int deltax, bool useEllipsis) = 0; + GUI::ThemeEngine::TextAlignVertical alignV, int deltax, bool useEllipsis, const Common::Rect &textDrawableArea) = 0; /** * Allows to temporarily enable/disable all shadows drawing. @@ -485,6 +496,7 @@ protected: Surface *_activeSurface; /**< Pointer to the surface currently being drawn */ FillMode _fillMode; /**< Defines in which way (if any) are filled the drawn shapes */ + ShadowFillMode _shadowFillMode; int _shadowOffset; /**< offset for drawn shadows */ int _bevel; /**< amount of fake bevel */ diff --git a/graphics/VectorRendererSpec.cpp b/graphics/VectorRendererSpec.cpp index 6a3ee306a5..491a9d7f42 100644 --- a/graphics/VectorRendererSpec.cpp +++ b/graphics/VectorRendererSpec.cpp @@ -119,6 +119,38 @@ inline frac_t fp_sqroot(uint32 x) { *(ptr4 + (y) + (px)) = color2; \ } while (0) +#define BE_DRAWCIRCLE_BCOLOR_TR_CW(ptr,x,y,px,py,a) do { \ + this->blendPixelPtr(ptr + (y) - (px), color, a); \ +} while (0) + +#define BE_DRAWCIRCLE_BCOLOR_TR_CCW(ptr,x,y,px,py,a) do { \ + this->blendPixelPtr(ptr + (x) - (py), color, a); \ +} while (0) + +#define BE_DRAWCIRCLE_BCOLOR_TL_CW(ptr,x,y,px,py,a) do { \ + this->blendPixelPtr(ptr - (x) - (py), color, a); \ +} while (0) + +#define BE_DRAWCIRCLE_BCOLOR_TL_CCW(ptr,x,y,px,py,a) do { \ + this->blendPixelPtr(ptr - (y) - (px), color, a); \ +} while (0) + +#define BE_DRAWCIRCLE_BCOLOR_BL_CW(ptr,x,y,px,py,a) do { \ + this->blendPixelPtr(ptr - (y) + (px), color, a); \ +} while (0) + +#define BE_DRAWCIRCLE_BCOLOR_BL_CCW(ptr,x,y,px,py,a) do { \ + this->blendPixelPtr(ptr - (x) + (py), color, a); \ +} while (0) + +#define BE_DRAWCIRCLE_BCOLOR_BR_CW(ptr,x,y,px,py,a) do { \ + this->blendPixelPtr(ptr + (x) + (py), color, a); \ +} while (0) + +#define BE_DRAWCIRCLE_BCOLOR_BR_CCW(ptr,x,y,px,py,a) do { \ + this->blendPixelPtr(ptr + (y) + (px), color, a); \ +} while (0) + #define BE_DRAWCIRCLE_XCOLOR_TOP(ptr1,ptr2,x,y,px,py) do { \ *(ptr1 + (y) - (px)) = color1; \ *(ptr1 + (x) - (py)) = color2; \ @@ -222,6 +254,37 @@ inline frac_t fp_sqroot(uint32 x) { this->blendPixelPtr(ptr4 + (y) + (px), color2, a); \ } while (0) +#define WU_DRAWCIRCLE_BCOLOR_TR_CW(ptr,x,y,px,py,a) do { \ + this->blendPixelPtr(ptr + (y) - (px), color, a); \ +} while (0) + +#define WU_DRAWCIRCLE_BCOLOR_TR_CCW(ptr,x,y,px,py,a) do { \ + this->blendPixelPtr(ptr + (x) - (py), color, a); \ +} while (0) + +#define WU_DRAWCIRCLE_BCOLOR_TL_CW(ptr,x,y,px,py,a) do { \ + this->blendPixelPtr(ptr - (x) - (py), color, a); \ +} while (0) + +#define WU_DRAWCIRCLE_BCOLOR_TL_CCW(ptr,x,y,px,py,a) do { \ + this->blendPixelPtr(ptr - (y) - (px), color, a); \ +} while (0) + +#define WU_DRAWCIRCLE_BCOLOR_BL_CW(ptr,x,y,px,py,a) do { \ + this->blendPixelPtr(ptr - (y) + (px), color, a); \ +} while (0) + +#define WU_DRAWCIRCLE_BCOLOR_BL_CCW(ptr,x,y,px,py,a) do { \ + this->blendPixelPtr(ptr - (x) + (py), color, a); \ +} while (0) + +#define WU_DRAWCIRCLE_BCOLOR_BR_CW(ptr,x,y,px,py,a) do { \ + this->blendPixelPtr(ptr + (x) + (py), color, a); \ +} while (0) + +#define WU_DRAWCIRCLE_BCOLOR_BR_CCW(ptr,x,y,px,py,a) do { \ + this->blendPixelPtr(ptr + (y) + (px), color, a); \ +} while (0) // optimized Wu's algorithm #define WU_ALGORITHM() do { \ @@ -277,16 +340,24 @@ void colorFill(PixelType *first, PixelType *last, PixelType color) { VectorRenderer *createRenderer(int mode) { #ifdef DISABLE_FANCY_THEMES - assert(mode == GUI::ThemeEngine::kGfxStandard16bit); + assert(mode == GUI::ThemeEngine::kGfxStandard); #endif PixelFormat format = g_system->getOverlayFormat(); switch (mode) { - case GUI::ThemeEngine::kGfxStandard16bit: - return new VectorRendererSpec<OverlayColor>(format); + case GUI::ThemeEngine::kGfxStandard: + if (g_system->getOverlayFormat().bytesPerPixel == 4) + return new VectorRendererSpec<uint32>(format); + else if (g_system->getOverlayFormat().bytesPerPixel == 2) + return new VectorRendererSpec<uint16>(format); + break; #ifndef DISABLE_FANCY_THEMES - case GUI::ThemeEngine::kGfxAntialias16bit: - return new VectorRendererAA<OverlayColor>(format); + case GUI::ThemeEngine::kGfxAntialias: + if (g_system->getOverlayFormat().bytesPerPixel == 4) + return new VectorRendererAA<uint32>(format); + else if (g_system->getOverlayFormat().bytesPerPixel == 2) + return new VectorRendererAA<uint16>(format); + break; #endif default: break; @@ -317,9 +388,15 @@ setGradientColors(uint8 r1, uint8 g1, uint8 b1, uint8 r2, uint8 g2, uint8 b2) { _gradientEnd = _format.RGBToColor(r2, g2, b2); _gradientStart = _format.RGBToColor(r1, g1, b1); - _gradientBytes[0] = (_gradientEnd & _redMask) - (_gradientStart & _redMask); - _gradientBytes[1] = (_gradientEnd & _greenMask) - (_gradientStart & _greenMask); - _gradientBytes[2] = (_gradientEnd & _blueMask) - (_gradientStart & _blueMask); + if (sizeof(PixelType) == 4) { + _gradientBytes[0] = ((_gradientEnd & _redMask) >> _format.rShift) - ((_gradientStart & _redMask) >> _format.rShift); + _gradientBytes[1] = ((_gradientEnd & _greenMask) >> _format.gShift) - ((_gradientStart & _greenMask) >> _format.gShift); + _gradientBytes[2] = ((_gradientEnd & _blueMask) >> _format.bShift) - ((_gradientStart & _blueMask) >> _format.bShift); + } else { + _gradientBytes[0] = (_gradientEnd & _redMask) - (_gradientStart & _redMask); + _gradientBytes[1] = (_gradientEnd & _greenMask) - (_gradientStart & _greenMask); + _gradientBytes[2] = (_gradientEnd & _blueMask) - (_gradientStart & _blueMask); + } } template<typename PixelType> @@ -328,9 +405,15 @@ calcGradient(uint32 pos, uint32 max) { PixelType output = 0; pos = (MIN(pos * Base::_gradientFactor, max) << 12) / max; - output |= ((_gradientStart & _redMask) + ((_gradientBytes[0] * pos) >> 12)) & _redMask; - output |= ((_gradientStart & _greenMask) + ((_gradientBytes[1] * pos) >> 12)) & _greenMask; - output |= ((_gradientStart & _blueMask) + ((_gradientBytes[2] * pos) >> 12)) & _blueMask; + if (sizeof(PixelType) == 4) { + output |= ((_gradientStart & _redMask) + (((_gradientBytes[0] * pos) >> 12) << _format.rShift)) & _redMask; + output |= ((_gradientStart & _greenMask) + (((_gradientBytes[1] * pos) >> 12) << _format.gShift)) & _greenMask; + output |= ((_gradientStart & _blueMask) + (((_gradientBytes[2] * pos) >> 12) << _format.bShift)) & _blueMask; + } else { + output |= ((_gradientStart & _redMask) + ((_gradientBytes[0] * pos) >> 12)) & _redMask; + output |= ((_gradientStart & _greenMask) + ((_gradientBytes[1] * pos) >> 12)) & _greenMask; + output |= ((_gradientStart & _blueMask) + ((_gradientBytes[2] * pos) >> 12)) & _blueMask; + } output |= _alphaMask; return output; @@ -397,7 +480,7 @@ gradientFill(PixelType *ptr, int width, int x, int y) { template<typename PixelType> void VectorRendererSpec<PixelType>:: fillSurface() { - byte *ptr = (byte *)_activeSurface->getBasePtr(0, 0); + byte *ptr = (byte *)_activeSurface->getPixels(); int h = _activeSurface->h; int pitch = _activeSurface->pitch; @@ -453,7 +536,7 @@ template<typename PixelType> void VectorRendererSpec<PixelType>:: blitSubSurface(const Graphics::Surface *source, const Common::Rect &r) { byte *dst_ptr = (byte *)_activeSurface->getBasePtr(r.left, r.top); - const byte *src_ptr = (const byte *)source->getBasePtr(0, 0); + const byte *src_ptr = (const byte *)source->getPixels(); const int dst_pitch = _activeSurface->pitch; const int src_pitch = source->pitch; @@ -481,7 +564,7 @@ blitAlphaBitmap(const Graphics::Surface *source, const Common::Rect &r) { y = y + (r.height() >> 1) - (source->h >> 1); PixelType *dst_ptr = (PixelType *)_activeSurface->getBasePtr(x, y); - const PixelType *src_ptr = (const PixelType *)source->getBasePtr(0, 0); + const PixelType *src_ptr = (const PixelType *)source->getPixels(); int dst_pitch = _activeSurface->pitch / _activeSurface->format.bytesPerPixel; int src_pitch = source->pitch / source->format.bytesPerPixel; @@ -508,7 +591,7 @@ template<typename PixelType> void VectorRendererSpec<PixelType>:: applyScreenShading(GUI::ThemeEngine::ShadingStyle shadingStyle) { int pixels = _activeSurface->w * _activeSurface->h; - PixelType *ptr = (PixelType *)_activeSurface->getBasePtr(0, 0); + PixelType *ptr = (PixelType *)_activeSurface->getPixels(); uint8 r, g, b; uint lum; @@ -537,20 +620,41 @@ applyScreenShading(GUI::ThemeEngine::ShadingStyle shadingStyle) { template<typename PixelType> inline void VectorRendererSpec<PixelType>:: blendPixelPtr(PixelType *ptr, PixelType color, uint8 alpha) { - int idst = *ptr; - int isrc = color; - - *ptr = (PixelType)( - (_redMask & ((idst & _redMask) + - ((int)(((int)(isrc & _redMask) - - (int)(idst & _redMask)) * alpha) >> 8))) | - (_greenMask & ((idst & _greenMask) + - ((int)(((int)(isrc & _greenMask) - - (int)(idst & _greenMask)) * alpha) >> 8))) | - (_blueMask & ((idst & _blueMask) + - ((int)(((int)(isrc & _blueMask) - - (int)(idst & _blueMask)) * alpha) >> 8))) | - (idst & _alphaMask)); + if (sizeof(PixelType) == 4) { + const byte sR = (color & _redMask) >> _format.rShift; + const byte sG = (color & _greenMask) >> _format.gShift; + const byte sB = (color & _blueMask) >> _format.bShift; + + byte dR = (*ptr & _redMask) >> _format.rShift; + byte dG = (*ptr & _greenMask) >> _format.gShift; + byte dB = (*ptr & _blueMask) >> _format.bShift; + + dR += ((sR - dR) * alpha) >> 8; + dG += ((sG - dG) * alpha) >> 8; + dB += ((sB - dB) * alpha) >> 8; + + *ptr = ((dR << _format.rShift) & _redMask) + | ((dG << _format.gShift) & _greenMask) + | ((dB << _format.bShift) & _blueMask) + | (*ptr & _alphaMask); + } else if (sizeof(PixelType) == 2) { + int idst = *ptr; + int isrc = color; + + *ptr = (PixelType)( + (_redMask & ((idst & _redMask) + + ((int)(((int)(isrc & _redMask) - + (int)(idst & _redMask)) * alpha) >> 8))) | + (_greenMask & ((idst & _greenMask) + + ((int)(((int)(isrc & _greenMask) - + (int)(idst & _greenMask)) * alpha) >> 8))) | + (_blueMask & ((idst & _blueMask) + + ((int)(((int)(isrc & _blueMask) - + (int)(idst & _blueMask)) * alpha) >> 8))) | + (idst & _alphaMask)); + } else { + error("Unsupported BPP format: %u", (uint)sizeof(PixelType)); + } } template<typename PixelType> @@ -607,24 +711,45 @@ darkenFill(PixelType *ptr, PixelType *end) { template<typename PixelType> void VectorRendererSpec<PixelType>:: drawString(const Graphics::Font *font, const Common::String &text, const Common::Rect &area, - Graphics::TextAlign alignH, GUI::ThemeEngine::TextAlignVertical alignV, int deltax, bool ellipsis) { + Graphics::TextAlign alignH, GUI::ThemeEngine::TextAlignVertical alignV, int deltax, bool ellipsis, const Common::Rect &textDrawableArea) { int offset = area.top; if (font->getFontHeight() < area.height()) { switch (alignV) { - case GUI::ThemeEngine::kTextAlignVCenter: - offset = area.top + ((area.height() - font->getFontHeight()) >> 1); - break; - case GUI::ThemeEngine::kTextAlignVBottom: - offset = area.bottom - font->getFontHeight(); - break; - default: - break; + case GUI::ThemeEngine::kTextAlignVCenter: + offset = area.top + ((area.height() - font->getFontHeight()) >> 1); + break; + case GUI::ThemeEngine::kTextAlignVBottom: + offset = area.bottom - font->getFontHeight(); + break; + default: + break; } } - font->drawString(_activeSurface, text, area.left, offset, area.width() - deltax, _fgColor, alignH, deltax, ellipsis); + Common::Rect drawArea; + if (textDrawableArea.isEmpty()) { + // In case no special area to draw to is given we only draw in the + // area specified by the user. + drawArea = area; + // warning("there is no text drawable area. Please set this area for clipping"); + } else { + // The area we can draw to is the intersection between the allowed + // drawing area (textDrawableArea) and the area where we try to draw + // the text (area). + drawArea = textDrawableArea.findIntersectingRect(area); + } + + // Better safe than sorry. We intersect with the actual surface boundaries + // to avoid any ugly clipping in _activeSurface->getSubArea which messes + // up the calculation of the x and y coordinates where to draw the string. + drawArea = drawArea.findIntersectingRect(Common::Rect(0, 0, _activeSurface->w, _activeSurface->h)); + + if (!drawArea.isEmpty()) { + Surface textAreaSurface = _activeSurface->getSubArea(drawArea); + font->drawString(&textAreaSurface, text, area.left - drawArea.left, offset - drawArea.top, area.width() - deltax, _fgColor, alignH, deltax, ellipsis); + } } /** LINES **/ @@ -778,7 +903,8 @@ drawRoundedSquare(int x, int y, int r, int w, int h) { if (Base::_fillMode != kFillDisabled && Base::_shadowOffset && x + w + Base::_shadowOffset + 1 < Base::_activeSurface->w - && y + h + Base::_shadowOffset + 1 < Base::_activeSurface->h) { + && y + h + Base::_shadowOffset + 1 < Base::_activeSurface->h + && h > (Base::_shadowOffset + 1) * 2) { drawRoundedSquareShadow(x, y, r, w, h, Base::_shadowOffset); } @@ -809,13 +935,14 @@ drawTab(int x, int y, int r, int w, int h) { // FIXME: This is broken for the AA renderer. // See the rounded rect alg for how to fix it. (The border should // be drawn before the interior, both inside drawTabAlg.) - drawTabAlg(x, y, w, h, r, (Base::_fillMode == kFillBackground) ? _bgColor : _fgColor, Base::_fillMode); + drawTabShadow(x, y, w - 2, h, r); + drawTabAlg(x, y, w - 2, h, r, _bgColor, Base::_fillMode); if (Base::_strokeWidth) drawTabAlg(x, y, w, h, r, _fgColor, kFillDisabled, (Base::_dynamicData >> 16), (Base::_dynamicData & 0xFFFF)); break; case kFillForeground: - drawTabAlg(x, y, w, h, r, (Base::_fillMode == kFillBackground) ? _bgColor : _fgColor, Base::_fillMode); + drawTabAlg(x, y, w, h, r, _fgColor, Base::_fillMode); break; } } @@ -996,6 +1123,67 @@ drawTabAlg(int x1, int y1, int w, int h, int r, PixelType color, VectorRenderer: } +template<typename PixelType> +void VectorRendererSpec<PixelType>:: +drawTabShadow(int x1, int y1, int w, int h, int r) { + int offset = 3; + int pitch = _activeSurface->pitch / _activeSurface->format.bytesPerPixel; + + // "Harder" shadows when having lower BPP, since we will have artifacts (greenish tint on the modern theme) + uint8 expFactor = 3; + uint16 alpha = (_activeSurface->format.bytesPerPixel > 2) ? 4 : 8; + + int xstart = x1; + int ystart = y1; + int width = w; + int height = h + offset + 1; + + for (int i = offset; i >= 0; i--) { + int f, ddF_x, ddF_y; + int x, y, px, py; + + PixelType *ptr_tl = (PixelType *)Base::_activeSurface->getBasePtr(xstart + r, ystart + r); + PixelType *ptr_tr = (PixelType *)Base::_activeSurface->getBasePtr(xstart + width - r, ystart + r); + PixelType *ptr_fill = (PixelType *)Base::_activeSurface->getBasePtr(xstart, ystart); + + int short_h = height - (2 * r) + 2; + PixelType color = _format.RGBToColor(0, 0, 0); + + BE_RESET(); + + // HACK: As we are drawing circles exploting 8-axis symmetry, + // there are 4 pixels on each circle which are drawn twice. + // this is ok on filled circles, but when blending on surfaces, + // we cannot let it blend twice. awful. + uint32 hb = 0; + + while (x++ < y) { + BE_ALGORITHM(); + + if (((1 << x) & hb) == 0) { + blendFill(ptr_tl - y - px, ptr_tr + y - px, color, (uint8)alpha); + hb |= (1 << x); + } + + if (((1 << y) & hb) == 0) { + blendFill(ptr_tl - x - py, ptr_tr + x - py, color, (uint8)alpha); + hb |= (1 << y); + } + } + + ptr_fill += pitch * r; + while (short_h--) { + blendFill(ptr_fill, ptr_fill + width + 1, color, (uint8)alpha); + ptr_fill += pitch; + } + + // Move shadow one pixel upward each iteration + xstart += 1; + // Multiply with expfactor + alpha = (alpha * (expFactor << 8)) >> 9; + } +} + /** BEVELED TABS FOR CLASSIC THEME **/ template<typename PixelType> void VectorRendererSpec<PixelType>:: @@ -1429,118 +1617,160 @@ drawTriangleFast(int x1, int y1, int size, bool inverted, PixelType color, Vecto /** ROUNDED SQUARE ALGORITHM **/ template<typename PixelType> void VectorRendererSpec<PixelType>:: -drawRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType color, VectorRenderer::FillMode fill_m) { +drawBorderRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType color, VectorRenderer::FillMode fill_m, uint8 alpha_t, uint8 alpha_r, uint8 alpha_b, uint8 alpha_l) { int f, ddF_x, ddF_y; int x, y, px, py; int pitch = _activeSurface->pitch / _activeSurface->format.bytesPerPixel; + int sw = 0, sp = 0, hp = h * pitch; - // TODO: Split this up into border, bevel and interior functions + PixelType *ptr_tl = (PixelType *)Base::_activeSurface->getBasePtr(x1 + r, y1 + r); + PixelType *ptr_tr = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - r, y1 + r); + PixelType *ptr_bl = (PixelType *)Base::_activeSurface->getBasePtr(x1 + r, y1 + h - r); + PixelType *ptr_br = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - r, y1 + h - r); + PixelType *ptr_fill = (PixelType *)Base::_activeSurface->getBasePtr(x1, y1); - if (fill_m != kFillDisabled) { - PixelType *ptr_tl = (PixelType *)Base::_activeSurface->getBasePtr(x1 + r, y1 + r); - PixelType *ptr_tr = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - r, y1 + r); - PixelType *ptr_bl = (PixelType *)Base::_activeSurface->getBasePtr(x1 + r, y1 + h - r); - PixelType *ptr_br = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - r, y1 + h - r); - PixelType *ptr_fill = (PixelType *)Base::_activeSurface->getBasePtr(x1, y1); - - int real_radius = r; - int short_h = h - (2 * r) + 2; - int long_h = h; + int real_radius = r; + int short_h = h - (2 * r) + 2; - BE_RESET(); + PixelType color1 = color; + PixelType color2 = color; - PixelType color1 = color; - if (fill_m == kFillBackground) - color1 = _bgColor; + while (sw++ < Base::_strokeWidth) { + blendFill(ptr_fill + sp + r, ptr_fill + w + 1 + sp - r, color1, alpha_t); // top + blendFill(ptr_fill + hp - sp + r, ptr_fill + w + hp + 1 - sp - r, color2, alpha_b); // bottom + sp += pitch; - if (fill_m == kFillGradient) { - PixelType color2, color3, color4; - precalcGradient(long_h); + BE_RESET(); + r--; + + int alphaStep_tr = ((alpha_t - alpha_r)/(y+1)); + int alphaStep_br = ((alpha_r - alpha_b)/(y+1)); + int alphaStep_bl = ((alpha_b - alpha_l)/(y+1)); + int alphaStep_tl = ((alpha_l - alpha_t)/(y+1)); + + // Avoid blending the last pixels twice, since we have an alpha + while (x++ < (y - 2)) { + BE_ALGORITHM(); - while (x++ < y) { - BE_ALGORITHM(); + BE_DRAWCIRCLE_BCOLOR_TR_CW(ptr_tr, x, y, px, py, (uint8)(alpha_r + (alphaStep_tr * x))); + BE_DRAWCIRCLE_BCOLOR_BR_CW(ptr_br, x, y, px, py, (uint8)(alpha_b + (alphaStep_br * x))); + BE_DRAWCIRCLE_BCOLOR_BL_CW(ptr_bl, x, y, px, py, (uint8)(alpha_l + (alphaStep_bl * x))); + BE_DRAWCIRCLE_BCOLOR_TL_CW(ptr_tl, x, y, px, py, (uint8)(alpha_t + (alphaStep_tl * x))); + + BE_DRAWCIRCLE_BCOLOR_TR_CCW(ptr_tr, x, y, px, py, (uint8)(alpha_t - (alphaStep_tr * x))); + BE_DRAWCIRCLE_BCOLOR_BR_CCW(ptr_br, x, y, px, py, (uint8)(alpha_r - (alphaStep_br * x))); + BE_DRAWCIRCLE_BCOLOR_BL_CCW(ptr_bl, x, y, px, py, (uint8)(alpha_b - (alphaStep_bl * x))); + BE_DRAWCIRCLE_BCOLOR_TL_CCW(ptr_tl, x, y, px, py, (uint8)(alpha_l - (alphaStep_tl * x))); + + if (Base::_strokeWidth > 1) { + BE_DRAWCIRCLE_BCOLOR(ptr_tr, ptr_tl, ptr_bl, ptr_br, x - 1, y, px, py); + BE_DRAWCIRCLE_BCOLOR(ptr_tr, ptr_tl, ptr_bl, ptr_br, x, y, px - pitch, py); + } + } + } - color1 = calcGradient(real_radius - x, long_h); - color2 = calcGradient(real_radius - y, long_h); - color3 = calcGradient(long_h - r + x, long_h); - color4 = calcGradient(long_h - r + y, long_h); + ptr_fill += pitch * real_radius; + while (short_h--) { + blendFill(ptr_fill, ptr_fill + Base::_strokeWidth, color1, alpha_l); // left + blendFill(ptr_fill + w - Base::_strokeWidth + 1, ptr_fill + w + 1, color2, alpha_r); // right + ptr_fill += pitch; + } +} - gradientFill(ptr_tl - x - py, w - 2 * r + 2 * x, x1 + r - x - y, real_radius - y); - gradientFill(ptr_tl - y - px, w - 2 * r + 2 * y, x1 + r - y - x, real_radius - x); +template<typename PixelType> +void VectorRendererSpec<PixelType>:: +drawInteriorRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType color, VectorRenderer::FillMode fill_m) { + int f, ddF_x, ddF_y; + int x, y, px, py; + int pitch = _activeSurface->pitch / _activeSurface->format.bytesPerPixel; - gradientFill(ptr_bl - x + py, w - 2 * r + 2 * x, x1 + r - x - y, long_h - r + y); - gradientFill(ptr_bl - y + px, w - 2 * r + 2 * y, x1 + r - y - x, long_h - r + x); + PixelType *ptr_tl = (PixelType *)Base::_activeSurface->getBasePtr(x1 + r, y1 + r); + PixelType *ptr_tr = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - r, y1 + r); + PixelType *ptr_bl = (PixelType *)Base::_activeSurface->getBasePtr(x1 + r, y1 + h - r); + PixelType *ptr_br = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - r, y1 + h - r); + PixelType *ptr_fill = (PixelType *)Base::_activeSurface->getBasePtr(x1, y1); - BE_DRAWCIRCLE_XCOLOR(ptr_tr, ptr_tl, ptr_bl, ptr_br, x, y, px, py); - } - } else { - while (x++ < y) { - BE_ALGORITHM(); + int real_radius = r; + int short_h = h - (2 * r) + 2; + int long_h = h; - colorFill<PixelType>(ptr_tl - x - py, ptr_tr + x - py, color1); - colorFill<PixelType>(ptr_tl - y - px, ptr_tr + y - px, color1); + BE_RESET(); - colorFill<PixelType>(ptr_bl - x + py, ptr_br + x + py, color1); - colorFill<PixelType>(ptr_bl - y + px, ptr_br + y + px, color1); + PixelType color1 = color; - // do not remove - messes up the drawing at lower resolutions - BE_DRAWCIRCLE(ptr_tr, ptr_tl, ptr_bl, ptr_br, x, y, px, py); - } - } + if (fill_m == kFillGradient) { + PixelType color2, color3, color4; + precalcGradient(long_h); - ptr_fill += pitch * r; - while (short_h--) { - if (fill_m == kFillGradient) { - gradientFill(ptr_fill, w + 1, x1, real_radius++); - } else { - colorFill<PixelType>(ptr_fill, ptr_fill + w + 1, color1); - } - ptr_fill += pitch; - } - } + while (x++ < y) { + BE_ALGORITHM(); + color1 = calcGradient(real_radius - x, long_h); + color2 = calcGradient(real_radius - y, long_h); + color3 = calcGradient(long_h - r + x, long_h); + color4 = calcGradient(long_h - r + y, long_h); - if (Base::_strokeWidth) { - int sw = 0, sp = 0, hp = h * pitch; + gradientFill(ptr_tl - x - py, w - 2 * r + 2 * x, x1 + r - x - y, real_radius - y); + gradientFill(ptr_tl - y - px, w - 2 * r + 2 * y, x1 + r - y - x, real_radius - x); - PixelType *ptr_tl = (PixelType *)Base::_activeSurface->getBasePtr(x1 + r, y1 + r); - PixelType *ptr_tr = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - r, y1 + r); - PixelType *ptr_bl = (PixelType *)Base::_activeSurface->getBasePtr(x1 + r, y1 + h - r); - PixelType *ptr_br = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - r, y1 + h - r); - PixelType *ptr_fill = (PixelType *)Base::_activeSurface->getBasePtr(x1, y1); + gradientFill(ptr_bl - x + py, w - 2 * r + 2 * x, x1 + r - x - y, long_h - r + y); + gradientFill(ptr_bl - y + px, w - 2 * r + 2 * y, x1 + r - y - x, long_h - r + x); - int real_radius = r; - int short_h = h - (2 * r) + 2; + BE_DRAWCIRCLE_XCOLOR(ptr_tr, ptr_tl, ptr_bl, ptr_br, x, y, px, py); + } + } else { + while (x++ < y) { + BE_ALGORITHM(); - // TODO: A gradient effect on the bevel - PixelType color1, color2; - color1 = Base::_bevel ? _bevelColor : color; - color2 = color; + colorFill<PixelType>(ptr_tl - x - py, ptr_tr + x - py, color1); + colorFill<PixelType>(ptr_tl - y - px, ptr_tr + y - px, color1); - while (sw++ < Base::_strokeWidth) { - colorFill<PixelType>(ptr_fill + sp + r, ptr_fill + w + 1 + sp - r, color1); - colorFill<PixelType>(ptr_fill + hp - sp + r, ptr_fill + w + hp + 1 - sp - r, color2); - sp += pitch; + colorFill<PixelType>(ptr_bl - x + py, ptr_br + x + py, color1); + colorFill<PixelType>(ptr_bl - y + px, ptr_br + y + px, color1); - BE_RESET(); - r--; - - while (x++ < y) { - BE_ALGORITHM(); - BE_DRAWCIRCLE_BCOLOR(ptr_tr, ptr_tl, ptr_bl, ptr_br, x, y, px, py); + // do not remove - messes up the drawing at lower resolutions + BE_DRAWCIRCLE(ptr_tr, ptr_tl, ptr_bl, ptr_br, x, y, px, py); + } + } - if (Base::_strokeWidth > 1) { - BE_DRAWCIRCLE_BCOLOR(ptr_tr, ptr_tl, ptr_bl, ptr_br, x - 1, y, px, py); - BE_DRAWCIRCLE_BCOLOR(ptr_tr, ptr_tl, ptr_bl, ptr_br, x, y, px - pitch, py); - } - } + ptr_fill += pitch * r; + while (short_h--) { + if (fill_m == kFillGradient) { + gradientFill(ptr_fill, w + 1, x1, real_radius++); + } else { + colorFill<PixelType>(ptr_fill, ptr_fill + w + 1, color1); } + ptr_fill += pitch; + } +} - ptr_fill += pitch * real_radius; - while (short_h--) { - colorFill<PixelType>(ptr_fill, ptr_fill + Base::_strokeWidth, color1); - colorFill<PixelType>(ptr_fill + w - Base::_strokeWidth + 1, ptr_fill + w + 1, color2); - ptr_fill += pitch; +template<typename PixelType> +void VectorRendererSpec<PixelType>:: +drawRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType color, VectorRenderer::FillMode fill_m) { + const uint8 borderAlpha_t = 0; + const uint8 borderAlpha_r = 127; + const uint8 borderAlpha_b = 255; + const uint8 borderAlpha_l = 63; + + const uint8 bevelAlpha_t = 255; + const uint8 bevelAlpha_r = 31; + const uint8 bevelAlpha_b = 0; + const uint8 bevelAlpha_l = 127; + + // If only border is visible + if ((!(w <= 0 || h <= 0)) && (fill_m != Base::kFillDisabled)) { + if (fill_m == Base::kFillBackground) + drawInteriorRoundedSquareAlg(x1, y1, r, w, h, _bgColor, fill_m); + else + drawInteriorRoundedSquareAlg(x1, y1, r, w, h, color, fill_m); + } + + if (Base::_strokeWidth) { + if (r != 0 && _bevel > 0) { + drawBorderRoundedSquareAlg(x1, y1, r, w, h, color, fill_m, borderAlpha_t, borderAlpha_r, borderAlpha_b, borderAlpha_l); + drawBorderRoundedSquareAlg(x1, y1, r, w, h, _bevelColor, fill_m, bevelAlpha_t, bevelAlpha_r, bevelAlpha_b, bevelAlpha_l); + } else { + drawBorderRoundedSquareAlg(x1, y1, r, w, h, color, fill_m, 255, 255, 255, 255); } } } @@ -1599,85 +1829,112 @@ drawCircleAlg(int x1, int y1, int r, PixelType color, VectorRenderer::FillMode f ********************************************************************/ template<typename PixelType> void VectorRendererSpec<PixelType>:: -drawSquareShadow(int x, int y, int w, int h, int blur) { - PixelType *ptr = (PixelType *)_activeSurface->getBasePtr(x + w - 1, y + blur); +drawSquareShadow(int x, int y, int w, int h, int offset) { + PixelType *ptr = (PixelType *)_activeSurface->getBasePtr(x + w - 1, y + offset); int pitch = _activeSurface->pitch / _activeSurface->format.bytesPerPixel; int i, j; - i = h - blur; + i = h - offset; while (i--) { - j = blur; + j = offset; while (j--) - blendPixelPtr(ptr + j, 0, ((blur - j) << 8) / blur); + blendPixelPtr(ptr + j, 0, ((offset - j) << 8) / offset); ptr += pitch; } - ptr = (PixelType *)_activeSurface->getBasePtr(x + blur, y + h - 1); + ptr = (PixelType *)_activeSurface->getBasePtr(x + offset, y + h - 1); - while (i++ < blur) { - j = w - blur; + while (i++ < offset) { + j = w - offset; while (j--) - blendPixelPtr(ptr + j, 0, ((blur - i) << 8) / blur); + blendPixelPtr(ptr + j, 0, ((offset - i) << 8) / offset); ptr += pitch; } ptr = (PixelType *)_activeSurface->getBasePtr(x + w, y + h); i = 0; - while (i++ < blur) { - j = blur - 1; + while (i++ < offset) { + j = offset - 1; while (j--) - blendPixelPtr(ptr + j, 0, (((blur - j) * (blur - i)) << 8) / (blur * blur)); + blendPixelPtr(ptr + j, 0, (((offset - j) * (offset - i)) << 8) / (offset * offset)); ptr += pitch; } } template<typename PixelType> void VectorRendererSpec<PixelType>:: -drawRoundedSquareShadow(int x1, int y1, int r, int w, int h, int blur) { - int f, ddF_x, ddF_y; - int x, y, px, py; +drawRoundedSquareShadow(int x1, int y1, int r, int w, int h, int offset) { int pitch = _activeSurface->pitch / _activeSurface->format.bytesPerPixel; - int alpha = 102; - x1 += blur; - y1 += blur; + // "Harder" shadows when having lower BPP, since we will have artifacts (greenish tint on the modern theme) + uint8 expFactor = 3; + uint16 alpha = (_activeSurface->format.bytesPerPixel > 2) ? 4 : 8; + + // These constants ensure a border of 2px on the left and of each rounded square + int xstart = (x1 > 2) ? x1 - 2 : x1; + int ystart = y1; + int width = w + offset + 2; + int height = h + offset + 1; + + for (int i = offset; i >= 0; i--) { + int f, ddF_x, ddF_y; + int x, y, px, py; + + PixelType *ptr_tl = (PixelType *)Base::_activeSurface->getBasePtr(xstart + r, ystart + r); + PixelType *ptr_tr = (PixelType *)Base::_activeSurface->getBasePtr(xstart + width - r, ystart + r); + PixelType *ptr_bl = (PixelType *)Base::_activeSurface->getBasePtr(xstart + r, ystart + height - r); + PixelType *ptr_br = (PixelType *)Base::_activeSurface->getBasePtr(xstart + width - r, ystart + height - r); + PixelType *ptr_fill = (PixelType *)Base::_activeSurface->getBasePtr(xstart, ystart); + + int short_h = height - (2 * r) + 2; + PixelType color = _format.RGBToColor(0, 0, 0); - PixelType *ptr_tr = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - r, y1 + r); - PixelType *ptr_bl = (PixelType *)Base::_activeSurface->getBasePtr(x1 + r, y1 + h - r); - PixelType *ptr_br = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - r, y1 + h - r); - PixelType *ptr_fill = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - blur, y1 + r); - - int short_h = h - (2 * r) + 1; + BE_RESET(); - BE_RESET(); + // HACK: As we are drawing circles exploting 8-axis symmetry, + // there are 4 pixels on each circle which are drawn twice. + // this is ok on filled circles, but when blending on surfaces, + // we cannot let it blend twice. awful. + uint32 hb = 0; + + while (x++ < y) { + BE_ALGORITHM(); - // HACK: As we are drawing circles exploting 8-axis symmetry, - // there are 4 pixels on each circle which are drawn twice. - // this is ok on filled circles, but when blending on surfaces, - // we cannot let it blend twice. awful. - uint32 hb = 0; - while (x++ < y) { - BE_ALGORITHM(); + if (((1 << x) & hb) == 0) { + blendFill(ptr_tl - y - px, ptr_tr + y - px, color, (uint8)alpha); + + // Will create a dark line of pixles if left out + if (hb > 0) { + blendFill(ptr_bl - y + px, ptr_br + y + px, color, (uint8)alpha); + } + hb |= (1 << x); + } - if (((1 << x) & hb) == 0) { - blendFill(ptr_tr - px - r, ptr_tr + y - px, 0, alpha); - blendFill(ptr_bl - y + px, ptr_br + y + px, 0, alpha); - hb |= (1 << x); + if (((1 << y) & hb) == 0) { + blendFill(ptr_tl - x - py, ptr_tr + x - py, color, (uint8)alpha); + blendFill(ptr_bl - x + py, ptr_br + x + py, color, (uint8)alpha); + hb |= (1 << y); + } } - - if (((1 << y) & hb) == 0) { - blendFill(ptr_tr - r - py, ptr_tr + x - py, 0, alpha); - blendFill(ptr_bl - x + py, ptr_br + x + py, 0, alpha); - hb |= (1 << y); + + ptr_fill += pitch * r; + while (short_h--) { + blendFill(ptr_fill, ptr_fill + width + 1, color, (uint8)alpha); + ptr_fill += pitch; } - } - while (short_h--) { - blendFill(ptr_fill - r, ptr_fill + blur, 0, alpha); - ptr_fill += pitch; + // Make shadow smaller each iteration, and move it one pixel inward + xstart += 1; + ystart += 1; + width -= 2; + height -= 2; + + if (_shadowFillMode == kShadowExponential) + // Multiply with expfactor + alpha = (alpha * (expFactor << 8)) >> 9; } } @@ -1870,7 +2127,7 @@ drawTabAlg(int x1, int y1, int w, int h, int r, PixelType color, VectorRenderer: /** ROUNDED SQUARES **/ template<typename PixelType> void VectorRendererAA<PixelType>:: -drawRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType color, VectorRenderer::FillMode fill_m) { +drawBorderRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType color, VectorRenderer::FillMode fill_m, uint8 alpha_t, uint8 alpha_r, uint8 alpha_b, uint8 alpha_l) { int x, y; const int pitch = Base::_activeSurface->pitch / Base::_activeSurface->format.bytesPerPixel; int px, py; @@ -1879,65 +2136,89 @@ drawRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType color, Vecto frac_t T = 0, oldT; uint8 a1, a2; - // TODO: Split this up into border, bevel and interior functions + PixelType *ptr_tl = (PixelType *)Base::_activeSurface->getBasePtr(x1 + r, y1 + r); + PixelType *ptr_tr = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - r, y1 + r); + PixelType *ptr_bl = (PixelType *)Base::_activeSurface->getBasePtr(x1 + r, y1 + h - r); + PixelType *ptr_br = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - r, y1 + h - r); + PixelType *ptr_fill = (PixelType *)Base::_activeSurface->getBasePtr(x1, y1); - if (Base::_strokeWidth) { - PixelType *ptr_tl = (PixelType *)Base::_activeSurface->getBasePtr(x1 + r, y1 + r); - PixelType *ptr_tr = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - r, y1 + r); - PixelType *ptr_bl = (PixelType *)Base::_activeSurface->getBasePtr(x1 + r, y1 + h - r); - PixelType *ptr_br = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - r, y1 + h - r); - PixelType *ptr_fill = (PixelType *)Base::_activeSurface->getBasePtr(x1, y1); - - int sw = 0, sp = 0; - int short_h = h - 2 * r; - int hp = h * pitch; - - int strokeWidth = Base::_strokeWidth; - // If we're going to fill the inside, draw a slightly thicker border - // so we can blend the inside on top of it. - if (fill_m != Base::kFillDisabled) strokeWidth++; - - // TODO: A gradient effect on the bevel - PixelType color1, color2; - color1 = Base::_bevel ? Base::_bevelColor : color; - color2 = color; + int sw = 0, sp = 0; + int short_h = h - 2 * r; + int hp = h * pitch; + int strokeWidth = Base::_strokeWidth; - while (sw++ < strokeWidth) { - colorFill<PixelType>(ptr_fill + sp + r, ptr_fill + w + 1 + sp - r, color1); - colorFill<PixelType>(ptr_fill + hp - sp + r, ptr_fill + w + hp + 1 - sp - r, color2); - sp += pitch; + while (sw++ < strokeWidth) { + this->blendFill(ptr_fill + hp - sp + r, ptr_fill + w + hp + 1 - sp - r, color, alpha_b); // bottom + this->blendFill(ptr_fill + sp + r, ptr_fill + w + 1 + sp - r, color, alpha_t); // top - x = r - (sw - 1); - y = 0; - T = 0; - px = pitch * x; - py = 0; + sp += pitch; - while (x > y++) { - WU_ALGORITHM(); + x = r - (sw - 1); + y = 0; + T = 0; + px = pitch * x; + py = 0; - // sw == 1: outside, sw = _strokeWidth: inside - // We always draw the outer edge AAed, but the inner edge - // only when the inside isn't filled - if (sw != strokeWidth || fill_m != Base::kFillDisabled) - a2 = 255; + int alphaStep_tr = ((alpha_t - alpha_r)/(x+1)); + int alphaStep_br = ((alpha_r - alpha_b)/(x+1)); + int alphaStep_bl = ((alpha_b - alpha_l)/(x+1)); + int alphaStep_tl = ((alpha_l - alpha_t)/(x+1)); - // inner arc - WU_DRAWCIRCLE_BCOLOR(ptr_tr, ptr_tl, ptr_bl, ptr_br, (x - 1), y, (px - pitch), py, a2); + while (x > y++) { + WU_ALGORITHM(); - if (sw == 1) // outer arc - WU_DRAWCIRCLE_BCOLOR(ptr_tr, ptr_tl, ptr_bl, ptr_br, x, y, px, py, a1); + // sw == 1: outside, sw = _strokeWidth: inside + // We always draw the outer edge AAed, but the inner edge + // only when the inside isn't filled + if (sw != strokeWidth || fill_m != Base::kFillDisabled) + a2 = 255; + + // inner arc + WU_DRAWCIRCLE_BCOLOR_TR_CW(ptr_tr, (x - 1), y, (px - pitch), py, (uint8)((uint32)(((alpha_t - (alphaStep_tr * y)) << 8) * a2) >> 16)); + WU_DRAWCIRCLE_BCOLOR_BR_CW(ptr_br, (x - 1), y, (px - pitch), py, (uint8)((uint32)(((alpha_r - (alphaStep_br * y)) << 8) * a2) >> 16)); + WU_DRAWCIRCLE_BCOLOR_BL_CW(ptr_bl, (x - 1), y, (px - pitch), py, (uint8)((uint32)(((alpha_b - (alphaStep_bl * y)) << 8) * a2) >> 16)); + WU_DRAWCIRCLE_BCOLOR_TL_CW(ptr_tl, (x - 1), y, (px - pitch), py, (uint8)((uint32)(((alpha_l - (alphaStep_tl * y)) << 8) * a2) >> 16)); + + WU_DRAWCIRCLE_BCOLOR_TR_CCW(ptr_tr, (x - 1), y, (px - pitch), py, (uint8)((uint32)(((alpha_r + (alphaStep_tr * y)) << 8) * a2) >> 16)); + WU_DRAWCIRCLE_BCOLOR_BR_CCW(ptr_br, (x - 1), y, (px - pitch), py, (uint8)((uint32)(((alpha_b + (alphaStep_br * y)) << 8) * a2) >> 16)); + WU_DRAWCIRCLE_BCOLOR_BL_CCW(ptr_bl, (x - 1), y, (px - pitch), py, (uint8)((uint32)(((alpha_l + (alphaStep_bl * y)) << 8) * a2) >> 16)); + WU_DRAWCIRCLE_BCOLOR_TL_CCW(ptr_tl, (x - 1), y, (px - pitch), py, (uint8)((uint32)(((alpha_t + (alphaStep_tl * y)) << 8) * a2) >> 16)); + + // outer arc + if (sw == 1) { + WU_DRAWCIRCLE_BCOLOR_TR_CW(ptr_tr, x, y, px, py, (uint8)((uint32)(((alpha_t - (alphaStep_tr * y)) << 8) * a1) >> 16)); + WU_DRAWCIRCLE_BCOLOR_BR_CW(ptr_br, x, y, px, py, (uint8)((uint32)(((alpha_r - (alphaStep_br * y)) << 8) * a1) >> 16)); + WU_DRAWCIRCLE_BCOLOR_BL_CW(ptr_bl, x, y, px, py, (uint8)((uint32)(((alpha_b - (alphaStep_bl * y)) << 8) * a1) >> 16)); + WU_DRAWCIRCLE_BCOLOR_TL_CW(ptr_tl, x, y, px, py, (uint8)((uint32)(((alpha_l - (alphaStep_tl * y)) << 8) * a1) >> 16)); + + WU_DRAWCIRCLE_BCOLOR_TR_CCW(ptr_tr, x, y, px, py, (uint8)((uint32)(((alpha_r + (alphaStep_tr * y)) << 8) * a1) >> 16)); + WU_DRAWCIRCLE_BCOLOR_BR_CCW(ptr_br, x, y, px, py, (uint8)((uint32)(((alpha_b + (alphaStep_br * y)) << 8) * a1) >> 16)); + WU_DRAWCIRCLE_BCOLOR_BL_CCW(ptr_bl, x, y, px, py, (uint8)((uint32)(((alpha_l + (alphaStep_bl * y)) << 8) * a1) >> 16)); + WU_DRAWCIRCLE_BCOLOR_TL_CCW(ptr_tl, x, y, px, py, (uint8)((uint32)(((alpha_t + (alphaStep_tl * y)) << 8) * a1) >> 16)); + } } - } ptr_fill += pitch * r; - while (short_h-- >= 0) { - colorFill<PixelType>(ptr_fill, ptr_fill + Base::_strokeWidth, color1); - colorFill<PixelType>(ptr_fill + w - Base::_strokeWidth + 1, ptr_fill + w + 1, color2); + + while (short_h-- >= -2) { + this->blendFill(ptr_fill, ptr_fill + Base::_strokeWidth, color, alpha_l); // left + this->blendFill(ptr_fill + w - Base::_strokeWidth + 1, ptr_fill + w + 1, color, alpha_r); // right ptr_fill += pitch; } } +} + +template<typename PixelType> +void VectorRendererAA<PixelType>:: +drawInteriorRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType color, VectorRenderer::FillMode fill_m) { + int x, y; + const int pitch = Base::_activeSurface->pitch / Base::_activeSurface->format.bytesPerPixel; + int px, py; + + uint32 rsq = r*r; + frac_t T = 0, oldT; + uint8 a1, a2; r -= Base::_strokeWidth; x1 += Base::_strokeWidth; @@ -1946,93 +2227,116 @@ drawRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType color, Vecto h -= 2*Base::_strokeWidth; rsq = r*r; - if (w <= 0 || h <= 0) - return; // Only border is visible + PixelType *ptr_tl = (PixelType *)Base::_activeSurface->getBasePtr(x1 + r, y1 + r); + PixelType *ptr_tr = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - r, y1 + r); + PixelType *ptr_bl = (PixelType *)Base::_activeSurface->getBasePtr(x1 + r, y1 + h - r); + PixelType *ptr_br = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - r, y1 + h - r); + PixelType *ptr_fill = (PixelType *)Base::_activeSurface->getBasePtr(x1, y1); - if (fill_m != Base::kFillDisabled) { - if (fill_m == Base::kFillBackground) - color = Base::_bgColor; + int short_h = h - 2 * r; + x = r; + y = 0; + T = 0; + px = pitch * x; + py = 0; - PixelType *ptr_tl = (PixelType *)Base::_activeSurface->getBasePtr(x1 + r, y1 + r); - PixelType *ptr_tr = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - r, y1 + r); - PixelType *ptr_bl = (PixelType *)Base::_activeSurface->getBasePtr(x1 + r, y1 + h - r); - PixelType *ptr_br = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w - r, y1 + h - r); - PixelType *ptr_fill = (PixelType *)Base::_activeSurface->getBasePtr(x1, y1); + if (fill_m == Base::kFillGradient) { - int short_h = h - 2 * r; - x = r; - y = 0; - T = 0; - px = pitch * x; - py = 0; + Base::precalcGradient(h); - if (fill_m == Base::kFillGradient) { + PixelType color1, color2, color3, color4; + while (x > y++) { + WU_ALGORITHM(); - Base::precalcGradient(h); + color1 = Base::calcGradient(r - x, h); + color2 = Base::calcGradient(r - y, h); + color3 = Base::calcGradient(h - r + x, h); + color4 = Base::calcGradient(h - r + y, h); - PixelType color1, color2, color3, color4; - while (x > y++) { - WU_ALGORITHM(); + Base::gradientFill(ptr_tl - x - py + 1, w - 2 * r + 2 * x - 1, x1 + r - x - y + 1, r - y); - color1 = Base::calcGradient(r - x, h); - color2 = Base::calcGradient(r - y, h); - color3 = Base::calcGradient(h - r + x, h); - color4 = Base::calcGradient(h - r + y, h); + // Only fill each horizontal line once (or we destroy + // the gradient effect at the edges) + if (T < oldT || y == 1) + Base::gradientFill(ptr_tl - y - px + 1, w - 2 * r + 2 * y - 1, x1 + r - y - x + 1, r - x); - Base::gradientFill(ptr_tl - x - py + 1, w - 2 * r + 2 * x - 1, x1 + r - x - y + 1, r - y); + Base::gradientFill(ptr_bl - x + py + 1, w - 2 * r + 2 * x - 1, x1 + r - x - y + 1, h - r + y); - // Only fill each horizontal line once (or we destroy - // the gradient effect at the edges) - if (T < oldT || y == 1) - Base::gradientFill(ptr_tl - y - px + 1, w - 2 * r + 2 * y - 1, x1 + r - y - x + 1, r - x); + // Only fill each horizontal line once (or we destroy + // the gradient effect at the edges) + if (T < oldT || y == 1) + Base::gradientFill(ptr_bl - y + px + 1, w - 2 * r + 2 * y - 1, x1 + r - y - x + 1, h - r + x); - Base::gradientFill(ptr_bl - x + py + 1, w - 2 * r + 2 * x - 1, x1 + r - x - y + 1, h - r + y); + // This shape is used for dialog backgrounds. + // If we're drawing on top of an empty overlay background, + // and the overlay supports alpha, we have to do AA by + // setting the dest alpha channel, instead of blending with + // dest color channels. + if (!g_system->hasFeature(OSystem::kFeatureOverlaySupportsAlpha)) + WU_DRAWCIRCLE_XCOLOR(ptr_tr, ptr_tl, ptr_bl, ptr_br, x, y, px, py, a1, blendPixelPtr); + else + WU_DRAWCIRCLE_XCOLOR(ptr_tr, ptr_tl, ptr_bl, ptr_br, x, y, px, py, a1, blendPixelDestAlphaPtr); + } - // Only fill each horizontal line once (or we destroy - // the gradient effect at the edges) - if (T < oldT || y == 1) - Base::gradientFill(ptr_bl - y + px + 1, w - 2 * r + 2 * y - 1, x1 + r - y - x + 1, h - r + x); - - // This shape is used for dialog backgrounds. - // If we're drawing on top of an empty overlay background, - // and the overlay supports alpha, we have to do AA by - // setting the dest alpha channel, instead of blending with - // dest color channels. - if (!g_system->hasFeature(OSystem::kFeatureOverlaySupportsAlpha)) - WU_DRAWCIRCLE_XCOLOR(ptr_tr, ptr_tl, ptr_bl, ptr_br, x, y, px, py, a1, blendPixelPtr); - else - WU_DRAWCIRCLE_XCOLOR(ptr_tr, ptr_tl, ptr_bl, ptr_br, x, y, px, py, a1, blendPixelDestAlphaPtr); - } + ptr_fill += pitch * r; + while (short_h-- >= 0) { + Base::gradientFill(ptr_fill, w + 1, x1, r++); + ptr_fill += pitch; + } - ptr_fill += pitch * r; - while (short_h-- >= 0) { - Base::gradientFill(ptr_fill, w + 1, x1, r++); - ptr_fill += pitch; - } + } else { - } else { + while (x > 1 + y++) { + WU_ALGORITHM(); - while (x > 1 + y++) { - WU_ALGORITHM(); + colorFill<PixelType>(ptr_tl - x - py + 1, ptr_tr + x - py, color); + if (T < oldT || y == 1) + colorFill<PixelType>(ptr_tl - y - px + 1, ptr_tr + y - px, color); - colorFill<PixelType>(ptr_tl - x - py + 1, ptr_tr + x - py, color); - if (T < oldT || y == 1) - colorFill<PixelType>(ptr_tl - y - px + 1, ptr_tr + y - px, color); + colorFill<PixelType>(ptr_bl - x + py + 1, ptr_br + x + py, color); + if (T < oldT || y == 1) + colorFill<PixelType>(ptr_bl - y + px + 1, ptr_br + y + px, color); - colorFill<PixelType>(ptr_bl - x + py + 1, ptr_br + x + py, color); - if (T < oldT || y == 1) - colorFill<PixelType>(ptr_bl - y + px + 1, ptr_br + y + px, color); + WU_DRAWCIRCLE(ptr_tr, ptr_tl, ptr_bl, ptr_br, x, y, px, py, a1); + } - WU_DRAWCIRCLE(ptr_tr, ptr_tl, ptr_bl, ptr_br, x, y, px, py, a1); - } + ptr_fill += pitch * r; + while (short_h-- >= 0) { + colorFill<PixelType>(ptr_fill, ptr_fill + w + 1, color); + ptr_fill += pitch; + } + } +} - ptr_fill += pitch * r; - while (short_h-- >= 0) { - colorFill<PixelType>(ptr_fill, ptr_fill + w + 1, color); - ptr_fill += pitch; - } +template<typename PixelType> +void VectorRendererAA<PixelType>:: +drawRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType color, VectorRenderer::FillMode fill_m) { + const uint8 borderAlpha_t = 0; + const uint8 borderAlpha_r = 127; + const uint8 borderAlpha_b = 255; + const uint8 borderAlpha_l = 63; + + const uint8 bevelAlpha_t = 255; + const uint8 bevelAlpha_r = 31; + const uint8 bevelAlpha_b = 0; + const uint8 bevelAlpha_l = 127; + + if (Base::_strokeWidth) { + if (r != 0 && Base::_bevel > 0) { + drawBorderRoundedSquareAlg(x1, y1, r, w, h, color, fill_m, borderAlpha_t, borderAlpha_r, borderAlpha_b, borderAlpha_l); + drawBorderRoundedSquareAlg(x1, y1, r, w, h, Base::_bevelColor, fill_m, bevelAlpha_t, bevelAlpha_r, bevelAlpha_b, bevelAlpha_l); + } else { + drawBorderRoundedSquareAlg(x1, y1, r, w, h, color, fill_m, 255, 255, 255, 255); } } + + // If only border is visible + if ((!(w <= 0 || h <= 0)) && (fill_m != Base::kFillDisabled)) { + if (fill_m == Base::kFillBackground) + drawInteriorRoundedSquareAlg(x1, y1, r, w, h, Base::_bgColor, fill_m); + else + drawInteriorRoundedSquareAlg(x1, y1, r, w, h, color, fill_m); + } } /** CIRCLES **/ diff --git a/graphics/VectorRendererSpec.h b/graphics/VectorRendererSpec.h index 4ed80cb55f..c035ca0e19 100644 --- a/graphics/VectorRendererSpec.h +++ b/graphics/VectorRendererSpec.h @@ -61,7 +61,7 @@ public: } void drawString(const Graphics::Font *font, const Common::String &text, const Common::Rect &area, Graphics::TextAlign alignH, - GUI::ThemeEngine::TextAlignVertical alignV, int deltax, bool elipsis); + GUI::ThemeEngine::TextAlignVertical alignV, int deltax, bool elipsis, const Common::Rect &textDrawableArea = Common::Rect(0, 0, 0, 0)); void setFgColor(uint8 r, uint8 g, uint8 b) { _fgColor = _format.RGBToColor(r, g, b); } void setBgColor(uint8 r, uint8 g, uint8 b) { _bgColor = _format.RGBToColor(r, g, b); } @@ -158,6 +158,12 @@ protected: virtual void drawRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType color, FillMode fill_m); + virtual void drawBorderRoundedSquareAlg(int x1, int y1, int r, int w, int h, + PixelType color, FillMode fill_m, uint8 alpha_t, uint8 alpha_r, uint8 alpha_b, uint8 alpha_l); + + virtual void drawInteriorRoundedSquareAlg(int x1, int y1, int r, int w, int h, + PixelType color, FillMode fill_m); + virtual void drawSquareAlg(int x, int y, int w, int h, PixelType color, FillMode fill_m); @@ -174,6 +180,8 @@ protected: PixelType color, VectorRenderer::FillMode fill_m, int baseLeft = 0, int baseRight = 0); + virtual void drawTabShadow(int x, int y, int w, int h, int r); + virtual void drawBevelTabAlg(int x, int y, int w, int h, int bevel, PixelType topColor, PixelType bottomColor, int baseLeft = 0, int baseRight = 0); @@ -186,10 +194,10 @@ protected: * There functions may be overloaded in inheriting classes to improve performance * in the slowest platforms where pixel alpha blending just doesn't cut it. * - * @param blur Intensity/size of the shadow. + * @param offset Intensity/size of the shadow. */ - virtual void drawSquareShadow(int x, int y, int w, int h, int blur); - virtual void drawRoundedSquareShadow(int x, int y, int r, int w, int h, int blur); + virtual void drawSquareShadow(int x, int y, int w, int h, int offset); + virtual void drawRoundedSquareShadow(int x, int y, int r, int w, int h, int offset); /** * Calculates the color gradient on a given point. @@ -292,10 +300,12 @@ protected: */ virtual void drawRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType color, VectorRenderer::FillMode fill_m); - virtual void drawRoundedSquareShadow(int x, int y, int r, int w, int h, int blur) { - Base::drawRoundedSquareShadow(x, y, r, w, h, blur); -// VectorRenderer::applyConvolutionMatrix(VectorRenderer::kConvolutionHardBlur, -// Common::Rect(x, y, x + w + blur * 2, y + h + blur * 2)); + virtual void drawBorderRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType color, VectorRenderer::FillMode fill_m, uint8 alpha_t, uint8 alpha_l, uint8 alpha_r, uint8 alpha_b); + + virtual void drawInteriorRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType color, VectorRenderer::FillMode fill_m); + + virtual void drawRoundedSquareShadow(int x, int y, int r, int w, int h, int offset) { + Base::drawRoundedSquareShadow(x, y, r, w, h, offset); } virtual void drawTabAlg(int x, int y, int w, int h, int r, diff --git a/graphics/cursorman.cpp b/graphics/cursorman.cpp index c818101645..133ee5fd71 100644 --- a/graphics/cursorman.cpp +++ b/graphics/cursorman.cpp @@ -48,6 +48,9 @@ bool CursorManager::isVisible() { bool CursorManager::showMouse(bool visible) { if (_cursorStack.empty()) return false; + if (_locked) { + return false; + } _cursorStack.top()->_visible = visible; @@ -225,6 +228,10 @@ void CursorManager::replaceCursorPalette(const byte *colors, uint start, uint nu } } +void CursorManager::lock(bool locked) { + _locked = locked; +} + CursorManager::Cursor::Cursor(const void *data, uint w, uint h, int hotspotX, int hotspotY, uint32 keycolor, bool dontScale, const Graphics::PixelFormat *format) { #ifdef USE_RGB_COLOR if (!format) @@ -246,6 +253,7 @@ CursorManager::Cursor::Cursor(const void *data, uint w, uint h, int hotspotX, in _hotspotX = hotspotX; _hotspotY = hotspotY; _dontScale = dontScale; + _visible = false; } CursorManager::Cursor::~Cursor() { diff --git a/graphics/cursorman.h b/graphics/cursorman.h index 66e8d1ba56..b4d8ad94ce 100644 --- a/graphics/cursorman.h +++ b/graphics/cursorman.h @@ -160,12 +160,15 @@ public: */ void replaceCursorPalette(const byte *colors, uint start, uint num); + void lock(bool locked); private: friend class Common::Singleton<SingletonBaseType>; // Even though this is basically the default constructor we implement it // ourselves, so it is private and thus there is no way to create this class // except from the Singleton code. - CursorManager() {} + CursorManager() { + _locked = false; + } ~CursorManager(); struct Cursor { @@ -198,6 +201,7 @@ private: }; Common::Stack<Cursor *> _cursorStack; Common::Stack<Palette *> _cursorPaletteStack; + bool _locked; }; } // End of namespace Graphics diff --git a/graphics/decoders/bmp.cpp b/graphics/decoders/bmp.cpp index bcfd0abbda..2eabbb7631 100644 --- a/graphics/decoders/bmp.cpp +++ b/graphics/decoders/bmp.cpp @@ -130,14 +130,14 @@ bool BitmapDecoder::loadStream(Common::SeekableReadStream &stream) { const int extraDataLength = (srcPitch % 4) ? 4 - (srcPitch % 4) : 0; if (bitsPerPixel == 8) { - byte *dst = (byte *)_surface->pixels; + byte *dst = (byte *)_surface->getPixels(); for (int32 i = 0; i < height; i++) { stream.read(dst + (height - i - 1) * width, width); stream.skip(extraDataLength); } } else if (bitsPerPixel == 24) { - byte *dst = (byte *)_surface->pixels + (height - 1) * _surface->pitch; + byte *dst = (byte *)_surface->getBasePtr(0, height - 1); for (int32 i = 0; i < height; i++) { for (uint32 j = 0; j < width; j++) { @@ -154,7 +154,7 @@ bool BitmapDecoder::loadStream(Common::SeekableReadStream &stream) { dst -= _surface->pitch * 2; } } else { // 32 bpp - byte *dst = (byte *)_surface->pixels + (height - 1) * _surface->pitch; + byte *dst = (byte *)_surface->getBasePtr(0, height - 1); for (int32 i = 0; i < height; i++) { for (uint32 j = 0; j < width; j++) { diff --git a/graphics/decoders/iff.cpp b/graphics/decoders/iff.cpp index 50c7b4f7de..fe27258d28 100644 --- a/graphics/decoders/iff.cpp +++ b/graphics/decoders/iff.cpp @@ -30,6 +30,10 @@ namespace Graphics { IFFDecoder::IFFDecoder() { _surface = 0; _palette = 0; + + // these 2 properties are not reset by destroy(), so the default is set here. + _numRelevantPlanes = 8; + _pixelPacking = false; destroy(); } @@ -54,8 +58,6 @@ void IFFDecoder::destroy() { _paletteRanges.clear(); _type = TYPE_UNKNOWN; _paletteColorCount = 0; - _numRelevantPlanes = 8; - _pixelPacking = false; } bool IFFDecoder::loadStream(Common::SeekableReadStream &stream) { @@ -170,7 +172,7 @@ void IFFDecoder::loadBitmap(Common::SeekableReadStream &stream) { if (_type == TYPE_ILBM) { uint32 scanlinePitch = ((_header.width + 15) >> 4) << 1; byte *scanlines = new byte[scanlinePitch * _header.numPlanes]; - byte *data = (byte *)_surface->pixels; + byte *data = (byte *)_surface->getPixels(); for (uint16 i = 0; i < _header.height; ++i) { byte *scanline = scanlines; @@ -194,7 +196,7 @@ void IFFDecoder::loadBitmap(Common::SeekableReadStream &stream) { delete[] scanlines; } else if (_type == TYPE_PBM) { - byte *data = (byte *)_surface->pixels; + byte *data = (byte *)_surface->getPixels(); uint32 outSize = _header.width * _header.height; if (_header.compression) { diff --git a/graphics/decoders/iff.h b/graphics/decoders/iff.h index beac62e519..37cb4b38c1 100644 --- a/graphics/decoders/iff.h +++ b/graphics/decoders/iff.h @@ -85,6 +85,8 @@ public: /** * The number of planes to decode, also determines the pixel packing if _packPixels is true. * 8 == decode all planes, map 1 pixel in 1 byte. (default, no packing even if _packPixels is true) + * + * NOTE: this property must be reset manually, and is not reset by a call to destroy(). */ void setNumRelevantPlanes(const uint8 numRelevantPlanes) { _numRelevantPlanes = numRelevantPlanes; } @@ -94,6 +96,8 @@ public: * 2 == decode first 2 planes, pack 4 pixels in 1 byte. This makes _surface->w 1/4th of _header.width * 4 == decode first 4 planes, pack 2 pixels in 1 byte. This makes _surface->w half of _header.width * Packed bitmaps won't have a proper surface format since there is no way to tell it to use 1, 2 or 4 bits per pixel + * + * NOTE: this property must be reset manually, and is not reset by a call to destroy(). */ void setPixelPacking(const bool pixelPacking) { _pixelPacking = pixelPacking; } private: diff --git a/graphics/decoders/image_decoder.h b/graphics/decoders/image_decoder.h index 49e31c6e3a..a39a9a1493 100644 --- a/graphics/decoders/image_decoder.h +++ b/graphics/decoders/image_decoder.h @@ -44,6 +44,9 @@ public: /** * Load an image from the specified stream + * + * loadStream() should implicitly call destroy() to free the memory + * of the last loadStream() call. * * @param stream the input stream * @return whether loading the file succeeded @@ -54,6 +57,9 @@ public: /** * Destroy this decoder's surface and palette + * + * This should be called by a loadStream() implementation as well + * as the destructor. */ virtual void destroy() = 0; diff --git a/graphics/decoders/jpeg.cpp b/graphics/decoders/jpeg.cpp index 75fdcd6e5a..c858884095 100644 --- a/graphics/decoders/jpeg.cpp +++ b/graphics/decoders/jpeg.cpp @@ -20,8 +20,11 @@ * */ +// libjpeg uses forbidden symbols in its header. Thus, we need to allow them +// here. +#define FORBIDDEN_SYMBOL_ALLOW_ALL + #include "graphics/pixelformat.h" -#include "graphics/yuv_to_rgb.h" #include "graphics/decoders/jpeg.h" #include "common/debug.h" @@ -29,35 +32,19 @@ #include "common/stream.h" #include "common/textconsole.h" -namespace Graphics { - -// Order used to traverse the quantization tables -static const uint8 _zigZagOrder[64] = { - 0, 1, 8, 16, 9, 2, 3, 10, - 17, 24, 32, 25, 18, 11, 4, 5, - 12, 19, 26, 33, 40, 48, 41, 34, - 27, 20, 13, 6, 7, 14, 21, 28, - 35, 42, 49, 56, 57, 50, 43, 36, - 29, 22, 15, 23, 30, 37, 44, 51, - 58, 59, 52, 45, 38, 31, 39, 46, - 53, 60, 61, 54, 47, 55, 62, 63 -}; - -JPEGDecoder::JPEGDecoder() : ImageDecoder(), - _stream(NULL), _w(0), _h(0), _numComp(0), _components(NULL), _numScanComp(0), - _scanComp(NULL), _currentComp(NULL), _rgbSurface(0) { +#ifdef USE_JPEG +// The original release of libjpeg v6b did not contain any extern "C" in case +// its header files are included in a C++ environment. To avoid any linking +// issues we need to add it on our own. +extern "C" { +#include <jpeglib.h> +#include <jerror.h> +} +#endif - // Initialize the quantization tables - for (int i = 0; i < JPEG_MAX_QUANT_TABLES; i++) - _quant[i] = NULL; +namespace Graphics { - // Initialize the Huffman tables - for (int i = 0; i < 2 * JPEG_MAX_HUFF_TABLES; i++) { - _huff[i].count = 0; - _huff[i].values = NULL; - _huff[i].sizes = NULL; - _huff[i].codes = NULL; - } +JPEGDecoder::JPEGDecoder() : ImageDecoder(), _surface(), _colorSpace(kColorSpaceRGBA) { } JPEGDecoder::~JPEGDecoder() { @@ -65,723 +52,215 @@ JPEGDecoder::~JPEGDecoder() { } const Surface *JPEGDecoder::getSurface() const { - // Make sure we have loaded data - if (!isLoaded()) - return 0; - - if (_rgbSurface) - return _rgbSurface; - - // Create an RGBA8888 surface - _rgbSurface = new Graphics::Surface(); - _rgbSurface->create(_w, _h, Graphics::PixelFormat(4, 8, 8, 8, 0, 24, 16, 8, 0)); - - // Get our component surfaces - const Graphics::Surface *yComponent = getComponent(1); - const Graphics::Surface *uComponent = getComponent(2); - const Graphics::Surface *vComponent = getComponent(3); - - YUVToRGBMan.convert444(_rgbSurface, Graphics::YUVToRGBManager::kScaleFull, (byte *)yComponent->pixels, (byte *)uComponent->pixels, (byte *)vComponent->pixels, yComponent->w, yComponent->h, yComponent->pitch, uComponent->pitch); - - return _rgbSurface; + return &_surface; } void JPEGDecoder::destroy() { - // Reset member variables - _stream = NULL; - _w = _h = 0; - _restartInterval = 0; - - // Free the components - for (int c = 0; c < _numComp; c++) - _components[c].surface.free(); - delete[] _components; _components = NULL; - _numComp = 0; - - // Free the scan components - delete[] _scanComp; _scanComp = NULL; - _numScanComp = 0; - _currentComp = NULL; - - // Free the quantization tables - for (int i = 0; i < JPEG_MAX_QUANT_TABLES; i++) { - delete[] _quant[i]; - _quant[i] = NULL; - } - - // Free the Huffman tables - for (int i = 0; i < 2 * JPEG_MAX_HUFF_TABLES; i++) { - _huff[i].count = 0; - delete[] _huff[i].values; _huff[i].values = NULL; - delete[] _huff[i].sizes; _huff[i].sizes = NULL; - delete[] _huff[i].codes; _huff[i].codes = NULL; - } - - if (_rgbSurface) { - _rgbSurface->free(); - delete _rgbSurface; - } + _surface.free(); } -bool JPEGDecoder::loadStream(Common::SeekableReadStream &stream) { - // Reset member variables and tables from previous reads - destroy(); - - // Save the input stream - _stream = &stream; - - bool ok = true; - bool done = false; - while (!_stream->eos() && ok && !done) { - // Read the marker - - // WORKAROUND: While each and every JPEG file should end with - // an EOI (end of image) tag, in reality this may not be the - // case. For instance, at least one image in the Masterpiece - // edition of Myst doesn't, yet other programs are able to read - // the image without complaining. - // - // Apparently, the customary workaround is to insert a fake - // EOI tag. - - uint16 marker = _stream->readByte(); - bool fakeEOI = false; - - if (_stream->eos()) { - fakeEOI = true; - marker = 0xFF; - } - - if (marker != 0xFF) { - error("JPEG: Invalid marker[0]: 0x%02X", marker); - ok = false; - break; - } - - while (marker == 0xFF && !_stream->eos()) - marker = _stream->readByte(); - - if (_stream->eos()) { - fakeEOI = true; - marker = 0xD9; - } - - if (fakeEOI) - warning("JPEG: Inserted fake EOI"); +#ifdef USE_JPEG +namespace { - // Process the marker data - switch (marker) { - case 0xC0: // Start Of Frame - ok = readSOF0(); - break; - case 0xC4: // Define Huffman Tables - ok = readDHT(); - break; - case 0xD8: // Start Of Image - break; - case 0xD9: // End Of Image - done = true; - break; - case 0xDA: // Start Of Scan - ok = readSOS(); - break; - case 0xDB: // Define Quantization Tables - ok = readDQT(); - break; - case 0xE0: // JFIF/JFXX segment - ok = readJFIF(); - break; - case 0xDD: // Define Restart Interval - ok = readDRI(); - break; - case 0xFE: // Comment - _stream->seek(_stream->readUint16BE() - 2, SEEK_CUR); - break; - default: { // Unknown marker - uint16 size = _stream->readUint16BE(); +#define JPEG_BUFFER_SIZE 4096 - if ((marker & 0xE0) != 0xE0) - warning("JPEG: Unknown marker %02X, skipping %d bytes", marker, size - 2); - - _stream->seek(size - 2, SEEK_CUR); - } - } - } +struct StreamSource : public jpeg_source_mgr { + Common::SeekableReadStream *stream; + bool startOfFile; + JOCTET buffer[JPEG_BUFFER_SIZE]; +}; - _stream = 0; - return ok; +void initSource(j_decompress_ptr cinfo) { + StreamSource *source = (StreamSource *)cinfo->src; + source->startOfFile = true; } -bool JPEGDecoder::readJFIF() { - uint16 length = _stream->readUint16BE(); - uint32 tag = _stream->readUint32BE(); +boolean fillInputBuffer(j_decompress_ptr cinfo) { + StreamSource *source = (StreamSource *)cinfo->src; - if (tag != MKTAG('J', 'F', 'I', 'F')) { - warning("JPEGDecoder::readJFIF() tag mismatch"); - return false; - } - - if (_stream->readByte() != 0) { // NULL - warning("JPEGDecoder::readJFIF() NULL mismatch"); - return false; + uint32 bufferSize = source->stream->read((byte *)source->buffer, sizeof(source->buffer)); + if (bufferSize == 0) { + if (source->startOfFile) { + // An empty file is a fatal error + ERREXIT(cinfo, JERR_INPUT_EMPTY); + } else { + // Otherwise we insert an EOF marker + WARNMS(cinfo, JWRN_JPEG_EOF); + source->buffer[0] = (JOCTET)0xFF; + source->buffer[1] = (JOCTET)JPEG_EOI; + bufferSize = 2; + } } - byte majorVersion = _stream->readByte(); - byte minorVersion = _stream->readByte(); - if (majorVersion != 1 || minorVersion > 2) - warning("JPEGDecoder::readJFIF(): v%d.%02d JPEGs may not be handled correctly", majorVersion, minorVersion); - - /* byte densityUnits = */_stream->readByte(); - /* uint16 xDensity = */_stream->readUint16BE(); - /* uint16 yDensity = */_stream->readUint16BE(); - byte thumbW = _stream->readByte(); - byte thumbH = _stream->readByte(); - - _stream->seek(thumbW * thumbH * 3, SEEK_CUR); // Ignore thumbnail - if (length != (thumbW * thumbH * 3) + 16) { - warning("JPEGDecoder::readJFIF() length mismatch"); - return false; - } + source->next_input_byte = source->buffer; + source->bytes_in_buffer = bufferSize; + source->startOfFile = false; - return true; + return TRUE; } -// Marker 0xC0 (Start Of Frame, Baseline DCT) -bool JPEGDecoder::readSOF0() { - debug(5, "JPEG: readSOF0"); - uint16 size = _stream->readUint16BE(); - - // Read the sample precision - uint8 precision = _stream->readByte(); - if (precision != 8) { - warning("JPEG: Just 8 bit precision supported at the moment"); - return false; - } +void skipInputData(j_decompress_ptr cinfo, long numBytes) { + StreamSource *source = (StreamSource *)cinfo->src; - // Image size - _h = _stream->readUint16BE(); - _w = _stream->readUint16BE(); - - // Number of components - _numComp = _stream->readByte(); - if (size != 8 + 3 * _numComp) { - warning("JPEG: Invalid number of components"); - return false; - } + if (numBytes > 0) { + if (numBytes > (long)source->bytes_in_buffer) { + // In case we need to skip more bytes than there are in the buffer + // we will skip the remaining data and fill the buffer again + numBytes -= (long)source->bytes_in_buffer; - // Allocate the new components - delete[] _components; - _components = new Component[_numComp]; - - // Read the components details - for (int c = 0; c < _numComp; c++) { - _components[c].id = _stream->readByte(); - _components[c].factorH = _stream->readByte(); - _components[c].factorV = _components[c].factorH & 0xF; - _components[c].factorH >>= 4; - _components[c].quantTableSelector = _stream->readByte(); - } - - return true; -} + // Skip the remaining bytes + source->stream->skip(numBytes); -// Marker 0xC4 (Define Huffman Tables) -bool JPEGDecoder::readDHT() { - debug(5, "JPEG: readDHT"); - uint16 size = _stream->readUint16BE() - 2; - uint32 pos = _stream->pos(); - - while ((uint32)_stream->pos() < (size + pos)) { - // Read the table type and id - uint8 tableId = _stream->readByte(); - uint8 tableType = tableId >> 4; // type 0: DC, 1: AC - tableId &= 0xF; - uint8 tableNum = (tableId << 1) + tableType; - - // Free the Huffman table - delete[] _huff[tableNum].values; _huff[tableNum].values = NULL; - delete[] _huff[tableNum].sizes; _huff[tableNum].sizes = NULL; - delete[] _huff[tableNum].codes; _huff[tableNum].codes = NULL; - - // Read the number of values for each length - uint8 numValues[16]; - _huff[tableNum].count = 0; - for (int len = 0; len < 16; len++) { - numValues[len] = _stream->readByte(); - _huff[tableNum].count += numValues[len]; - } - - // Allocate memory for the current table - _huff[tableNum].values = new uint8[_huff[tableNum].count]; - _huff[tableNum].sizes = new uint8[_huff[tableNum].count]; - _huff[tableNum].codes = new uint16[_huff[tableNum].count]; - - // Read the table contents - int cur = 0; - for (int len = 0; len < 16; len++) { - for (int i = 0; i < numValues[len]; i++) { - _huff[tableNum].values[cur] = _stream->readByte(); - _huff[tableNum].sizes[cur] = len + 1; - cur++; - } + // Fill up the buffer again + (*source->fill_input_buffer)(cinfo); + } else { + source->next_input_byte += (size_t)numBytes; + source->bytes_in_buffer -= (size_t)numBytes; } - // Fill the table of Huffman codes - cur = 0; - uint16 curCode = 0; - uint8 curCodeSize = _huff[tableNum].sizes[0]; - while (cur < _huff[tableNum].count) { - // Increase the code size to fit the request - while (_huff[tableNum].sizes[cur] != curCodeSize) { - curCode <<= 1; - curCodeSize++; - } - - // Assign the current code - _huff[tableNum].codes[cur] = curCode; - curCode++; - cur++; - } } - - return true; } -// Marker 0xDA (Start Of Scan) -bool JPEGDecoder::readSOS() { - debug(5, "JPEG: readSOS"); - uint16 size = _stream->readUint16BE(); - - // Number of scan components - _numScanComp = _stream->readByte(); - if (size != 6 + 2 * _numScanComp) { - warning("JPEG: Invalid number of components"); - return false; - } - - // Allocate the new scan components - delete[] _scanComp; - _scanComp = new Component *[_numScanComp]; - - // Reset the maximum sampling factors - _maxFactorV = 0; - _maxFactorH = 0; - - // Component-specification parameters - for (int c = 0; c < _numScanComp; c++) { - // Read the desired component id - uint8 id = _stream->readByte(); - - // Search the component with the specified id - bool found = false; - for (int i = 0; !found && i < _numComp; i++) { - if (_components[i].id == id) { - // We found the desired component - found = true; - - // Assign the found component to the c'th scan component - _scanComp[c] = &_components[i]; - } - } - - if (!found) { - warning("JPEG: Invalid component"); - return false; - } - - // Read the entropy table selectors - _scanComp[c]->DCentropyTableSelector = _stream->readByte(); - _scanComp[c]->ACentropyTableSelector = _scanComp[c]->DCentropyTableSelector & 0xF; - _scanComp[c]->DCentropyTableSelector >>= 4; - - // Calculate the maximum sampling factors - if (_scanComp[c]->factorV > _maxFactorV) - _maxFactorV = _scanComp[c]->factorV; - - if (_scanComp[c]->factorH > _maxFactorH) - _maxFactorH = _scanComp[c]->factorH; - - // Initialize the DC predictor - _scanComp[c]->DCpredictor = 0; - } - - // Start of spectral selection - if (_stream->readByte() != 0) { - warning("JPEG: Progressive scanning not supported"); - return false; - } - - // End of spectral selection - if (_stream->readByte() != 63) { - warning("JPEG: Progressive scanning not supported"); - return false; - } - - // Successive approximation parameters - if (_stream->readByte() != 0) { - warning("JPEG: Progressive scanning not supported"); - return false; - } - - // Entropy coded sequence starts, initialize Huffman decoder - _bitsNumber = 0; - - // Read all the scan MCUs - uint16 xMCU = _w / (_maxFactorH * 8); - uint16 yMCU = _h / (_maxFactorV * 8); - - // Check for non- multiple-of-8 dimensions - if (_w % (_maxFactorH * 8) != 0) - xMCU++; - if (_h % (_maxFactorV * 8) != 0) - yMCU++; - - // Initialize the scan surfaces - for (uint16 c = 0; c < _numScanComp; c++) { - _scanComp[c]->surface.create(xMCU * _maxFactorH * 8, yMCU * _maxFactorV * 8, PixelFormat::createFormatCLUT8()); - } - - bool ok = true; - uint16 interval = _restartInterval; - - for (int y = 0; ok && (y < yMCU); y++) { - for (int x = 0; ok && (x < xMCU); x++) { - ok = readMCU(x, y); - - // If we have a restart interval, we'll need to reset a couple - // variables - if (_restartInterval != 0) { - interval--; - - if (interval == 0) { - interval = _restartInterval; - _bitsNumber = 0; - - for (byte i = 0; i < _numScanComp; i++) - _scanComp[i]->DCpredictor = 0; - } - } - } - } - - // Trim Component surfaces back to image height and width - // Note: Code using jpeg must use surface.pitch correctly... - for (uint16 c = 0; c < _numScanComp; c++) { - _scanComp[c]->surface.w = _w; - _scanComp[c]->surface.h = _h; - } - - return ok; +void termSource(j_decompress_ptr cinfo) { } -// Marker 0xDB (Define Quantization Tables) -bool JPEGDecoder::readDQT() { - debug(5, "JPEG: readDQT"); - uint16 size = _stream->readUint16BE() - 2; - uint32 pos = _stream->pos(); - - while ((uint32)_stream->pos() < (pos + size)) { - // Read the table precision and id - uint8 tableId = _stream->readByte(); - bool highPrecision = (tableId & 0xF0) != 0; - - // Validate the table id - tableId &= 0xF; - if (tableId >= JPEG_MAX_QUANT_TABLES) { - warning("JPEG: Invalid quantization table"); - return false; - } - - // Create the new table if necessary - if (!_quant[tableId]) - _quant[tableId] = new uint16[64]; +void jpeg_scummvm_src(j_decompress_ptr cinfo, Common::SeekableReadStream *stream) { + StreamSource *source; - // Read the table (stored in Zig-Zag order) - for (int i = 0; i < 64; i++) - _quant[tableId][i] = highPrecision ? _stream->readUint16BE() : _stream->readByte(); + // Initialize the source in case it has not been done yet. + if (cinfo->src == NULL) { + cinfo->src = (jpeg_source_mgr *)(*cinfo->mem->alloc_small)((j_common_ptr)cinfo, JPOOL_PERMANENT, sizeof(StreamSource)); } - return true; -} + source = (StreamSource *)cinfo->src; + source->init_source = &initSource; + source->fill_input_buffer = &fillInputBuffer; + source->skip_input_data = &skipInputData; + source->resync_to_restart = &jpeg_resync_to_restart; + source->term_source = &termSource; + source->bytes_in_buffer = 0; + source->next_input_byte = NULL; -// Marker 0xDD (Define Restart Interval) -bool JPEGDecoder::readDRI() { - debug(5, "JPEG: readDRI"); - uint16 size = _stream->readUint16BE() - 2; - - if (size != 2) { - warning("JPEG: Invalid DRI size %d", size); - return false; - } - - _restartInterval = _stream->readUint16BE(); - debug(5, "Restart interval: %d", _restartInterval); - return true; + source->stream = stream; } -bool JPEGDecoder::readMCU(uint16 xMCU, uint16 yMCU) { - bool ok = true; - for (int c = 0; ok && (c < _numComp); c++) { - // Set the current component - _currentComp = _scanComp[c]; - - // Read the data units of the current component - for (int y = 0; ok && (y < _scanComp[c]->factorV); y++) - for (int x = 0; ok && (x < _scanComp[c]->factorH); x++) - ok = readDataUnit(xMCU * _scanComp[c]->factorH + x, yMCU * _scanComp[c]->factorV + y); - } - - return ok; +void errorExit(j_common_ptr cinfo) { + char buffer[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message)(cinfo, buffer); + // This function is not allowed to return to the caller, thus we simply + // error out with our error handling here. + error("%s", buffer); } -// triple-butterfly-add (and possible rounding) -#define xadd3(xa, xb, xc, xd, h) \ - p = xa + xb; \ - n = xa - xb; \ - xa = p + xc + h; \ - xb = n + xd + h; \ - xc = p - xc + h; \ - xd = n - xd + h; - -// butterfly-mul -#define xmul(xa, xb, k1, k2, sh) \ - n = k1 * (xa + xb); \ - p = xa; \ - xa = (n + (k2 - k1) * xb) >> sh; \ - xb = (n - (k2 + k1) * p) >> sh; - -// IDCT based on public domain code from http://halicery.com/jpeg/idct.html -void JPEGDecoder::idct1D8x8(int32 src[8], int32 dest[64], int32 ps, int32 half) { - int p, n; - - src[0] <<= 9; - src[1] <<= 7; - src[3] *= 181; - src[4] <<= 9; - src[5] *= 181; - src[7] <<= 7; - - // Even part - xmul(src[6], src[2], 277, 669, 0) - xadd3(src[0], src[4], src[6], src[2], half) - - // Odd part - xadd3(src[1], src[7], src[3], src[5], 0) - xmul(src[5], src[3], 251, 50, 6) - xmul(src[1], src[7], 213, 142, 6) - - dest[0 * 8] = (src[0] + src[1]) >> ps; - dest[1 * 8] = (src[4] + src[5]) >> ps; - dest[2 * 8] = (src[2] + src[3]) >> ps; - dest[3 * 8] = (src[6] + src[7]) >> ps; - dest[4 * 8] = (src[6] - src[7]) >> ps; - dest[5 * 8] = (src[2] - src[3]) >> ps; - dest[6 * 8] = (src[4] - src[5]) >> ps; - dest[7 * 8] = (src[0] - src[1]) >> ps; +void outputMessage(j_common_ptr cinfo) { + char buffer[JMSG_LENGTH_MAX]; + (*cinfo->err->format_message)(cinfo, buffer); + // Is using debug here a good idea? Or do we want to ignore all libjpeg + // messages? + debug(3, "libjpeg: %s", buffer); } -void JPEGDecoder::idct2D8x8(int32 block[64]) { - int32 tmp[64]; - - // Apply 1D IDCT to rows - for (int i = 0; i < 8; i++) - idct1D8x8(&block[i * 8], &tmp[i], 9, 1 << 8); - - // Apply 1D IDCT to columns - for (int i = 0; i < 8; i++) - idct1D8x8(&tmp[i * 8], &block[i], 12, 1 << 11); - } - -bool JPEGDecoder::readDataUnit(uint16 x, uint16 y) { - // Prepare an empty data array - int16 readData[64]; - for (int i = 1; i < 64; i++) - readData[i] = 0; - - // Read the DC component - readData[0] = _currentComp->DCpredictor + readDC(); - _currentComp->DCpredictor = readData[0]; - - // Read the AC components (stored in Zig-Zag) - readAC(readData); - - // Calculate the DCT coefficients from the input sequence - int32 block[64]; - for (uint8 i = 0; i < 64; i++) { - // Dequantize - int32 val = readData[i]; - int16 quant = _quant[_currentComp->quantTableSelector][i]; - val *= quant; - - // Store the normalized coefficients, undoing the Zig-Zag - block[_zigZagOrder[i]] = val; - } +} // End of anonymous namespace +#endif - // Apply the IDCT - idct2D8x8(block); +bool JPEGDecoder::loadStream(Common::SeekableReadStream &stream) { +#ifdef USE_JPEG + // Reset member variables from previous decodings + destroy(); - // Level shift to make the values unsigned - for (int i = 0; i < 64; i++) { - block[i] = block[i] + 128; + jpeg_decompress_struct cinfo; + jpeg_error_mgr jerr; - if (block[i] < 0) - block[i] = 0; + // Initialize error handling callbacks + cinfo.err = jpeg_std_error(&jerr); + cinfo.err->error_exit = &errorExit; + cinfo.err->output_message = &outputMessage; - if (block[i] > 255) - block[i] = 255; - } + // Initialize the decompression structure + jpeg_create_decompress(&cinfo); - // Paint the component surface - uint8 scalingV = _maxFactorV / _currentComp->factorV; - uint8 scalingH = _maxFactorH / _currentComp->factorH; + // Initialize our buffer handling + jpeg_scummvm_src(&cinfo, &stream); - // Convert coordinates from MCU blocks to pixels - x <<= 3; - y <<= 3; + // Read the file header + jpeg_read_header(&cinfo, TRUE); - for (uint8 j = 0; j < 8; j++) { - for (uint16 sV = 0; sV < scalingV; sV++) { - // Get the beginning of the block line - byte *ptr = (byte *)_currentComp->surface.getBasePtr(x * scalingH, (y + j) * scalingV + sV); + // We can request YUV output because Groovie requires it + switch (_colorSpace) { + case kColorSpaceRGBA: + cinfo.out_color_space = JCS_RGB; + break; - for (uint8 i = 0; i < 8; i++) { - for (uint16 sH = 0; sH < scalingH; sH++) { - *ptr = (byte)(block[j * 8 + i]); - ptr++; - } - } - } + case kColorSpaceYUV: + cinfo.out_color_space = JCS_YCbCr; + break; } - return true; -} - -int16 JPEGDecoder::readDC() { - // DC is type 0 - uint8 tableNum = _currentComp->DCentropyTableSelector << 1; - - // Get the number of bits to read - uint8 numBits = readHuff(tableNum); - - // Read the requested bits - return readSignedBits(numBits); -} - -void JPEGDecoder::readAC(int16 *out) { - // AC is type 1 - uint8 tableNum = (_currentComp->ACentropyTableSelector << 1) + 1; - - // Start reading AC element 1 - uint8 cur = 1; - while (cur < 64) { - uint8 s = readHuff(tableNum); - uint8 r = s >> 4; - s &= 0xF; - - if (s == 0) { - if (r == 15) { - // Skip 16 values - cur += 16; - } else { - // EOB: end of block - cur = 64; - } - } else { - // Skip r values - cur += r; - - // Read the next value - out[cur] = readSignedBits(s); - cur++; - } + // Actually start decompressing the image + jpeg_start_decompress(&cinfo); + + // Allocate buffers for the output data + switch (_colorSpace) { + case kColorSpaceRGBA: + // We use RGBA8888 in this scenario + _surface.create(cinfo.output_width, cinfo.output_height, Graphics::PixelFormat(4, 8, 8, 8, 0, 24, 16, 8, 0)); + break; + + case kColorSpaceYUV: + // We use YUV with 3 bytes per pixel otherwise. + // This is pretty ugly since our PixelFormat cannot express YUV... + _surface.create(cinfo.output_width, cinfo.output_height, Graphics::PixelFormat(3, 0, 0, 0, 0, 0, 0, 0, 0)); + break; } -} - -int16 JPEGDecoder::readSignedBits(uint8 numBits) { - uint16 ret = 0; - if (numBits > 16) - error("requested %d bits", numBits); //XXX - - // MSB=0 for negatives, 1 for positives - for (int i = 0; i < numBits; i++) - ret = (ret << 1) + readBit(); - - // Extend sign bits (PAG109) - if (!(ret >> (numBits - 1))) { - uint16 tmp = ((uint16)-1 << numBits) + 1; - ret = ret + tmp; - } - return ret; -} - -// TODO: optimize? -uint8 JPEGDecoder::readHuff(uint8 table) { - bool foundCode = false; - uint8 val = 0; - - uint8 cur = 0; - uint8 codeSize = 1; - uint16 code = readBit(); - while (!foundCode) { - // Prepare a code of the current size - while (codeSize < _huff[table].sizes[cur]) { - code = (code << 1) + readBit(); - codeSize++; - } - // Compare the codes of the current size - while (!foundCode && (codeSize == _huff[table].sizes[cur])) { - if (code == _huff[table].codes[cur]) { - // Found the code - val = _huff[table].values[cur]; - foundCode = true; - } else { - // Continue reading - cur++; + // Allocate buffer for one scanline + assert(cinfo.output_components == 3); + JDIMENSION pitch = cinfo.output_width * cinfo.output_components; + assert(_surface.pitch >= pitch); + JSAMPARRAY buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, pitch, 1); + + // Go through the image data scanline by scanline + while (cinfo.output_scanline < cinfo.output_height) { + byte *dst = (byte *)_surface.getBasePtr(0, cinfo.output_scanline); + + jpeg_read_scanlines(&cinfo, buffer, 1); + + const byte *src = buffer[0]; + switch (_colorSpace) { + case kColorSpaceRGBA: { + for (int remaining = cinfo.output_width; remaining > 0; --remaining) { + byte r = *src++; + byte g = *src++; + byte b = *src++; + // We need to insert a alpha value of 255 (opaque) here. +#ifdef SCUMM_BIG_ENDIAN + *dst++ = r; + *dst++ = g; + *dst++ = b; + *dst++ = 0xFF; +#else + *dst++ = 0xFF; + *dst++ = b; + *dst++ = g; + *dst++ = r; +#endif } - } - } + } break; - return val; -} - -uint8 JPEGDecoder::readBit() { - // Read a whole byte if necessary - if (_bitsNumber == 0) { - _bitsData = _stream->readByte(); - _bitsNumber = 8; - - // Detect markers - if (_bitsData == 0xFF) { - uint8 byte2 = _stream->readByte(); - - // A stuffed 0 validates the previous byte - if (byte2 != 0) { - if (byte2 == 0xDC) { - // DNL marker: Define Number of Lines - // TODO: terminate scan - warning("DNL marker detected: terminate scan"); - } else if (byte2 >= 0xD0 && byte2 <= 0xD7) { - debug(7, "RST%d marker detected", byte2 & 7); - _bitsData = _stream->readByte(); - } else { - warning("Error: marker 0x%02X read in entropy data", byte2); - } - } + case kColorSpaceYUV: + memcpy(dst, src, pitch); + break; } } - _bitsNumber--; - - return (_bitsData & (1 << _bitsNumber)) ? 1 : 0; -} -const Surface *JPEGDecoder::getComponent(uint c) const { - for (int i = 0; i < _numComp; i++) - if (_components[i].id == c) // We found the desired component - return &_components[i].surface; + // We are done with decompressing, thus free all the data + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); - error("JPEGDecoder::getComponent: No component %d present", c); - return NULL; + return true; +#else + return false; +#endif } } // End of Graphics namespace diff --git a/graphics/decoders/jpeg.h b/graphics/decoders/jpeg.h index d59b72adf4..8460bc2698 100644 --- a/graphics/decoders/jpeg.h +++ b/graphics/decoders/jpeg.h @@ -40,100 +40,54 @@ class SeekableReadStream; namespace Graphics { -struct PixelFormat; - -#define JPEG_MAX_QUANT_TABLES 4 -#define JPEG_MAX_HUFF_TABLES 2 - class JPEGDecoder : public ImageDecoder { public: JPEGDecoder(); ~JPEGDecoder(); // ImageDecoder API - void destroy(); - bool loadStream(Common::SeekableReadStream &str); - const Surface *getSurface() const; - - bool isLoaded() const { return _numComp && _w && _h; } - uint16 getWidth() const { return _w; } - uint16 getHeight() const { return _h; } - const Surface *getComponent(uint c) const; - -private: - Common::SeekableReadStream *_stream; - uint16 _w, _h; - uint16 _restartInterval; - - // mutable so that we can convert to RGB only during - // a getSurface() call while still upholding the - // const requirement in other ImageDecoders - mutable Graphics::Surface *_rgbSurface; - - // Image components - uint8 _numComp; - struct Component { - // Global values - uint8 id; - uint8 factorH; - uint8 factorV; - uint8 quantTableSelector; - - // Scan specific values - uint8 DCentropyTableSelector; - uint8 ACentropyTableSelector; - int16 DCpredictor; - - // Result image for this component - Surface surface; + virtual void destroy(); + virtual bool loadStream(Common::SeekableReadStream &str); + virtual const Surface *getSurface() const; + + // Special API for JPEG + enum ColorSpace { + /** + * Output 32bit RGBA data. + * + * This is the default output. + */ + kColorSpaceRGBA, + + /** + * Output (interleaved) YUV data. + * + * Be aware that some images cannot be output in YUV mode. + * These are (non-standard) JPEG images which are in RGB colorspace. + * + * The resulting Surface will have a PixelFormat with 3 bytes per + * pixel and the remaining entries are completely zeroed. This works + * around the fact that PixelFormat can only describe RGB formats. + * + * You should only use this when you are really aware of what you are + * doing! + */ + kColorSpaceYUV }; - Component *_components; + /** + * Request the output color space. This can be used to obtain raw YUV + * data from the JPEG file. But this might not work for all files! + * + * The decoder itself defaults to RGBA. + * + * @param outSpace The color space to output. + */ + void setOutputColorSpace(ColorSpace outSpace) { _colorSpace = outSpace; } - // Scan components - uint8 _numScanComp; - Component **_scanComp; - Component *_currentComp; - - // Maximum sampling factors, used to calculate the interleaving of the MCU - uint8 _maxFactorV; - uint8 _maxFactorH; - - // Quantization tables - uint16 *_quant[JPEG_MAX_QUANT_TABLES]; - - // Huffman tables - struct HuffmanTable { - uint8 count; - uint8 *values; - uint8 *sizes; - uint16 *codes; - } _huff[2 * JPEG_MAX_HUFF_TABLES]; - - // Marker read functions - bool readJFIF(); - bool readSOF0(); - bool readDHT(); - bool readSOS(); - bool readDQT(); - bool readDRI(); - - // Helper functions - bool readMCU(uint16 xMCU, uint16 yMCU); - bool readDataUnit(uint16 x, uint16 y); - int16 readDC(); - void readAC(int16 *out); - int16 readSignedBits(uint8 numBits); - - // Huffman decoding - uint8 readHuff(uint8 table); - uint8 readBit(); - uint8 _bitsData; - uint8 _bitsNumber; - - // Inverse Discrete Cosine Transformation - static void idct1D8x8(int32 src[8], int32 dest[64], int32 ps, int32 half); - static void idct2D8x8(int32 block[64]); +private: + Graphics::Surface _surface; + ColorSpace _colorSpace; }; } // End of Graphics namespace diff --git a/graphics/decoders/pcx.cpp b/graphics/decoders/pcx.cpp index 1250398c73..eb9b4c997d 100644 --- a/graphics/decoders/pcx.cpp +++ b/graphics/decoders/pcx.cpp @@ -117,7 +117,7 @@ bool PCXDecoder::loadStream(Common::SeekableReadStream &stream) { if (nPlanes == 3 && bitsPerPixel == 8) { // 24bpp Graphics::PixelFormat format = Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0); _surface->create(width, height, format); - dst = (byte *)_surface->pixels; + dst = (byte *)_surface->getPixels(); _paletteColorCount = 0; for (y = 0; y < height; y++) { @@ -135,7 +135,7 @@ bool PCXDecoder::loadStream(Common::SeekableReadStream &stream) { } } else if (nPlanes == 1 && bitsPerPixel == 8) { // 8bpp indexed _surface->create(width, height, Graphics::PixelFormat::createFormatCLUT8()); - dst = (byte *)_surface->pixels; + dst = (byte *)_surface->getPixels(); _paletteColorCount = 16; for (y = 0; y < height; y++, dst += _surface->pitch) { @@ -163,7 +163,7 @@ bool PCXDecoder::loadStream(Common::SeekableReadStream &stream) { } } else if ((nPlanes == 2 || nPlanes == 3 || nPlanes == 4) && bitsPerPixel == 1) { // planar, 4, 8 or 16 colors _surface->create(width, height, Graphics::PixelFormat::createFormatCLUT8()); - dst = (byte *)_surface->pixels; + dst = (byte *)_surface->getPixels(); _paletteColorCount = 16; for (y = 0; y < height; y++, dst += _surface->pitch) { diff --git a/graphics/decoders/pict.cpp b/graphics/decoders/pict.cpp index b1d408ebc3..f3e17b33e2 100644 --- a/graphics/decoders/pict.cpp +++ b/graphics/decoders/pict.cpp @@ -364,7 +364,7 @@ void PICTDecoder::unpackBitsRect(Common::SeekableReadStream &stream, bool withPa case 1: // Just copy to the image _outputSurface->create(width, height, PixelFormat::createFormatCLUT8()); - memcpy(_outputSurface->pixels, buffer, _outputSurface->w * _outputSurface->h); + memcpy(_outputSurface->getPixels(), buffer, _outputSurface->w * _outputSurface->h); break; case 2: // We have a 16-bit surface diff --git a/graphics/decoders/png.cpp b/graphics/decoders/png.cpp index 11e26162eb..c4fee46fec 100644 --- a/graphics/decoders/png.cpp +++ b/graphics/decoders/png.cpp @@ -38,7 +38,7 @@ namespace Graphics { -PNGDecoder::PNGDecoder() : _outputSurface(0), _palette(0), _paletteColorCount(0) { +PNGDecoder::PNGDecoder() : _outputSurface(0), _palette(0), _paletteColorCount(0), _stream(0) { } PNGDecoder::~PNGDecoder() { @@ -163,16 +163,20 @@ bool PNGDecoder::loadStream(Common::SeekableReadStream &stream) { _outputSurface->create(width, height, Graphics::PixelFormat::createFormatCLUT8()); png_set_packing(pngPtr); } else { - _outputSurface->create(width, height, Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0)); - if (!_outputSurface->pixels) { + bool isAlpha = (colorType & PNG_COLOR_MASK_ALPHA); + if (png_get_valid(pngPtr, infoPtr, PNG_INFO_tRNS)) { + isAlpha = true; + png_set_expand(pngPtr); + } + _outputSurface->create(width, height, Graphics::PixelFormat(4, + 8, 8, 8, isAlpha ? 8 : 0, 24, 16, 8, 0)); + if (!_outputSurface->getPixels()) { error("Could not allocate memory for output image."); } if (bitDepth == 16) png_set_strip_16(pngPtr); if (bitDepth < 8) png_set_expand(pngPtr); - if (png_get_valid(pngPtr, infoPtr, PNG_INFO_tRNS)) - png_set_expand(pngPtr); if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) png_set_gray_to_rgb(pngPtr); diff --git a/graphics/decoders/tga.cpp b/graphics/decoders/tga.cpp index c3b9d84055..bc27f18a49 100644 --- a/graphics/decoders/tga.cpp +++ b/graphics/decoders/tga.cpp @@ -50,6 +50,8 @@ void TGADecoder::destroy() { } bool TGADecoder::loadStream(Common::SeekableReadStream &tga) { + destroy(); + byte imageType, pixelDepth; bool success; success = readHeader(tga, imageType, pixelDepth); @@ -272,7 +274,7 @@ bool TGADecoder::readData(Common::SeekableReadStream &tga, byte imageType, byte } else if (imageType == TYPE_BW) { _surface.create(_surface.w, _surface.h, _format); - byte *data = (byte *)_surface.pixels; + byte *data = (byte *)_surface.getPixels(); uint32 count = _surface.w * _surface.h; while (count-- > 0) { @@ -318,7 +320,7 @@ bool TGADecoder::readDataRLE(Common::SeekableReadStream &tga, byte imageType, by if (imageType == TYPE_RLE_TRUECOLOR || imageType == TYPE_RLE_BW || imageType == TYPE_RLE_CMAP) { _surface.create(_surface.w, _surface.h, _format); uint32 count = _surface.w * _surface.h; - byte *data = (byte *)_surface.pixels; + byte *data = (byte *)_surface.getPixels(); while (count > 0) { uint32 header = tga.readByte(); diff --git a/graphics/decoders/tga.h b/graphics/decoders/tga.h index d8ccf8f766..5b1e87d4c5 100644 --- a/graphics/decoders/tga.h +++ b/graphics/decoders/tga.h @@ -26,6 +26,7 @@ /* * TGA decoder used in engines: * - wintermute + * - zvision */ #ifndef GRAPHICS_DECODERS_TGA_H diff --git a/graphics/font.cpp b/graphics/font.cpp index 3b00cd8568..e9f139c478 100644 --- a/graphics/font.cpp +++ b/graphics/font.cpp @@ -26,93 +26,32 @@ namespace Graphics { -int Font::getKerningOffset(byte left, byte right) const { +int Font::getKerningOffset(uint32 left, uint32 right) const { return 0; } -int Font::getStringWidth(const Common::String &str) const { +namespace { + +template<class StringType> +int getStringWidthImpl(const Font &font, const StringType &str) { int space = 0; - uint last = 0; + typename StringType::unsigned_type last = 0; for (uint i = 0; i < str.size(); ++i) { - const uint cur = str[i]; - space += getCharWidth(cur) + getKerningOffset(last, cur); + const typename StringType::unsigned_type cur = str[i]; + space += font.getCharWidth(cur) + font.getKerningOffset(last, cur); last = cur; } return space; } -void Font::drawString(Surface *dst, const Common::String &sOld, int x, int y, int w, uint32 color, TextAlign align, int deltax, bool useEllipsis) const { +template<class StringType> +void drawStringImpl(const Font &font, Surface *dst, const StringType &str, int x, int y, int w, uint32 color, TextAlign align, int deltax) { assert(dst != 0); - const int leftX = x, rightX = x + w; - uint i; - Common::String s = sOld; - int width = getStringWidth(s); - Common::String str; - - if (useEllipsis && width > w && s.hasSuffix("...")) { - // String is too wide. Check whether it ends in an ellipsis - // ("..."). If so, remove that and try again! - s.deleteLastChar(); - s.deleteLastChar(); - s.deleteLastChar(); - width = getStringWidth(s); - } - - if (useEllipsis && width > w) { - // String is too wide. So we shorten it "intelligently" by - // replacing parts of the string by an ellipsis. There are - // three possibilities for this: replace the start, the end, or - // the middle of the string. What is best really depends on the - // context; but unless we want to make this configurable, - // replacing the middle seems to be a good compromise. - - const int ellipsisWidth = getStringWidth("..."); - - // SLOW algorithm to remove enough of the middle. But it is good enough - // for now. - const int halfWidth = (w - ellipsisWidth) / 2; - int w2 = 0; - uint last = 0; - - for (i = 0; i < s.size(); ++i) { - const uint cur = s[i]; - int charWidth = getCharWidth(cur) + getKerningOffset(last, cur); - if (w2 + charWidth > halfWidth) - break; - last = cur; - w2 += charWidth; - str += cur; - } - - // At this point we know that the first 'i' chars are together 'w2' - // pixels wide. We took the first i-1, and add "..." to them. - str += "..."; - last = '.'; - // The original string is width wide. Of those we already skipped past - // w2 pixels, which means (width - w2) remain. - // The new str is (w2+ellipsisWidth) wide, so we can accommodate about - // (w - (w2+ellipsisWidth)) more pixels. - // Thus we skip ((width - w2) - (w - (w2+ellipsisWidth))) = - // (width + ellipsisWidth - w) - int skip = width + ellipsisWidth - w; - for (; i < s.size() && skip > 0; ++i) { - const uint cur = s[i]; - skip -= getCharWidth(cur) + getKerningOffset(last, cur); - last = cur; - } - - // Append the remaining chars, if any - for (; i < s.size(); ++i) { - str += s[i]; - } - - width = getStringWidth(str); - } else { - str = s; - } + const int leftX = x, rightX = x + w; + int width = font.getStringWidth(str); if (align == kTextAlignCenter) x = x + (w - width)/2; @@ -120,29 +59,29 @@ void Font::drawString(Surface *dst, const Common::String &sOld, int x, int y, in x = x + w - width; x += deltax; - uint last = 0; - for (i = 0; i < str.size(); ++i) { - const uint cur = str[i]; - x += getKerningOffset(last, cur); + typename StringType::unsigned_type last = 0; + for (typename StringType::const_iterator i = str.begin(), end = str.end(); i != end; ++i) { + const typename StringType::unsigned_type cur = *i; + x += font.getKerningOffset(last, cur); last = cur; - w = getCharWidth(cur); + w = font.getCharWidth(cur); if (x+w > rightX) break; - if (x >= leftX) - drawChar(dst, str[i], x, y, color); + if (x+w >= leftX) + font.drawChar(dst, cur, x, y, color); x += w; } } - +template<class StringType> struct WordWrapper { - Common::Array<Common::String> &lines; + Common::Array<StringType> &lines; int actualMaxLineWidth; - WordWrapper(Common::Array<Common::String> &l) : lines(l), actualMaxLineWidth(0) { + WordWrapper(Common::Array<StringType> &l) : lines(l), actualMaxLineWidth(0) { } - void add(Common::String &line, int &w) { + void add(StringType &line, int &w) { if (actualMaxLineWidth < w) actualMaxLineWidth = w; @@ -153,10 +92,11 @@ struct WordWrapper { } }; -int Font::wordWrapText(const Common::String &str, int maxWidth, Common::Array<Common::String> &lines) const { - WordWrapper wrapper(lines); - Common::String line; - Common::String tmpStr; +template<class StringType> +int wordWrapTextImpl(const Font &font, const StringType &str, int maxWidth, Common::Array<StringType> &lines) { + WordWrapper<StringType> wrapper(lines); + StringType line; + StringType tmpStr; int lineWidth = 0; int tmpWidth = 0; @@ -173,10 +113,10 @@ int Font::wordWrapText(const Common::String &str, int maxWidth, Common::Array<Co // of a line. If we encounter such a word, we have to wrap it over multiple // lines. - uint last = 0; - for (Common::String::const_iterator x = str.begin(); x != str.end(); ++x) { - const byte c = *x; - const int w = getCharWidth(c) + getKerningOffset(last, c); + typename StringType::unsigned_type last = 0; + for (typename StringType::const_iterator x = str.begin(); x != str.end(); ++x) { + const typename StringType::unsigned_type c = *x; + const int w = font.getCharWidth(c) + font.getKerningOffset(last, c); last = c; const bool wouldExceedWidth = (lineWidth + tmpWidth + w > maxWidth); @@ -212,7 +152,7 @@ int Font::wordWrapText(const Common::String &str, int maxWidth, Common::Array<Co tmpStr.deleteChar(0); // This is not very fast, but it is the simplest way to // assure we do not mess something up because of kerning. - tmpWidth = getStringWidth(tmpStr); + tmpWidth = font.getStringWidth(tmpStr); } } else { wrapper.add(tmpStr, tmpWidth); @@ -232,5 +172,98 @@ int Font::wordWrapText(const Common::String &str, int maxWidth, Common::Array<Co return wrapper.actualMaxLineWidth; } +} // End of anonymous namespace + +int Font::getStringWidth(const Common::String &str) const { + return getStringWidthImpl(*this, str); +} + +int Font::getStringWidth(const Common::U32String &str) const { + return getStringWidthImpl(*this, str); +} + +void Font::drawString(Surface *dst, const Common::String &sOld, int x, int y, int w, uint32 color, TextAlign align, int deltax, bool useEllipsis) const { + Common::String s = sOld; + int width = getStringWidth(s); + Common::String str; + + if (useEllipsis && width > w && s.hasSuffix("...")) { + // String is too wide. Check whether it ends in an ellipsis + // ("..."). If so, remove that and try again! + s.deleteLastChar(); + s.deleteLastChar(); + s.deleteLastChar(); + width = getStringWidth(s); + } + + if (useEllipsis && width > w) { + // String is too wide. So we shorten it "intelligently" by + // replacing parts of the string by an ellipsis. There are + // three possibilities for this: replace the start, the end, or + // the middle of the string. What is best really depends on the + // context; but unless we want to make this configurable, + // replacing the middle seems to be a good compromise. + + const int ellipsisWidth = getStringWidth("..."); + + // SLOW algorithm to remove enough of the middle. But it is good enough + // for now. + const int halfWidth = (w - ellipsisWidth) / 2; + int w2 = 0; + Common::String::unsigned_type last = 0; + uint i; + + for (i = 0; i < s.size(); ++i) { + const Common::String::unsigned_type cur = s[i]; + int charWidth = getCharWidth(cur) + getKerningOffset(last, cur); + if (w2 + charWidth > halfWidth) + break; + last = cur; + w2 += charWidth; + str += cur; + } + + // At this point we know that the first 'i' chars are together 'w2' + // pixels wide. We took the first i-1, and add "..." to them. + str += "..."; + last = '.'; + + // The original string is width wide. Of those we already skipped past + // w2 pixels, which means (width - w2) remain. + // The new str is (w2+ellipsisWidth) wide, so we can accommodate about + // (w - (w2+ellipsisWidth)) more pixels. + // Thus we skip ((width - w2) - (w - (w2+ellipsisWidth))) = + // (width + ellipsisWidth - w) + int skip = width + ellipsisWidth - w; + for (; i < s.size() && skip > 0; ++i) { + const Common::String::unsigned_type cur = s[i]; + skip -= getCharWidth(cur) + getKerningOffset(last, cur); + last = cur; + } + + // Append the remaining chars, if any + for (; i < s.size(); ++i) { + str += s[i]; + } + + width = getStringWidth(str); + } else { + str = s; + } + + drawStringImpl(*this, dst, str, x, y, w, color, align, deltax); +} + +void Font::drawString(Surface *dst, const Common::U32String &str, int x, int y, int w, uint32 color, TextAlign align) const { + drawStringImpl(*this, dst, str, x, y, w, color, align, 0); +} + +int Font::wordWrapText(const Common::String &str, int maxWidth, Common::Array<Common::String> &lines) const { + return wordWrapTextImpl(*this, str, maxWidth, lines); +} + +int Font::wordWrapText(const Common::U32String &str, int maxWidth, Common::Array<Common::U32String> &lines) const { + return wordWrapTextImpl(*this, str, maxWidth, lines); +} } // End of namespace Graphics diff --git a/graphics/font.h b/graphics/font.h index 6819b42f52..77b7623a85 100644 --- a/graphics/font.h +++ b/graphics/font.h @@ -23,6 +23,7 @@ #define GRAPHICS_FONT_H #include "common/str.h" +#include "common/ustr.h" namespace Common { template<class T> class Array; @@ -70,7 +71,7 @@ public: * @param chr The character to query the width of. * @return The character's width. */ - virtual int getCharWidth(byte chr) const = 0; + virtual int getCharWidth(uint32 chr) const = 0; /** * Query the kerning offset between two characters. @@ -79,7 +80,7 @@ public: * @param right The right character. May be 0. * @return The horizontal displacement. */ - virtual int getKerningOffset(byte left, byte right) const; + virtual int getKerningOffset(uint32 left, uint32 right) const; /** * Draw a character at a specific point on a surface. @@ -96,15 +97,17 @@ public: * @param y The y coordinate where to draw the character. * @param color The color of the character. */ - virtual void drawChar(Surface *dst, byte chr, int x, int y, uint32 color) const = 0; + virtual void drawChar(Surface *dst, uint32 chr, int x, int y, uint32 color) const = 0; // TODO: Add doxygen comments to this void drawString(Surface *dst, const Common::String &str, int x, int y, int w, uint32 color, TextAlign align = kTextAlignLeft, int deltax = 0, bool useEllipsis = true) const; + void drawString(Surface *dst, const Common::U32String &str, int x, int y, int w, uint32 color, TextAlign align = kTextAlignLeft) const; /** * Compute and return the width the string str has when rendered using this font. */ int getStringWidth(const Common::String &str) const; + int getStringWidth(const Common::U32String &str) const; /** * Take a text (which may contain newline characters) and word wrap it so that @@ -120,6 +123,7 @@ public: * @return the maximal width of any of the lines added to lines */ int wordWrapText(const Common::String &str, int maxWidth, Common::Array<Common::String> &lines) const; + int wordWrapText(const Common::U32String &str, int maxWidth, Common::Array<Common::U32String> &lines) const; }; } // End of namespace Graphics diff --git a/graphics/fonts/bdf.cpp b/graphics/fonts/bdf.cpp index 6d4befa37c..7b2290cc3a 100644 --- a/graphics/fonts/bdf.cpp +++ b/graphics/fonts/bdf.cpp @@ -51,7 +51,7 @@ int BdfFont::getMaxCharWidth() const { return _data.maxAdvance; } -int BdfFont::getCharWidth(byte chr) const { +int BdfFont::getCharWidth(uint32 chr) const { // In case all font have the same advance value, we use the maximum. if (!_data.advances) return _data.maxAdvance; @@ -85,9 +85,9 @@ void drawCharIntern(byte *ptr, uint pitch, const byte *src, int h, int width, in } } -int BdfFont::mapToIndex(byte ch) const { +int BdfFont::mapToIndex(uint32 ch) const { // Check whether the character is included - if (_data.firstCharacter <= ch && ch <= _data.firstCharacter + _data.numCharacters) { + if (_data.firstCharacter <= (int)ch && (int)ch <= _data.firstCharacter + _data.numCharacters) { if (_data.bitmaps[ch - _data.firstCharacter]) return ch - _data.firstCharacter; } @@ -95,14 +95,14 @@ int BdfFont::mapToIndex(byte ch) const { return _data.defaultCharacter - _data.firstCharacter; } -void BdfFont::drawChar(Surface *dst, byte chr, const int tx, const int ty, const uint32 color) const { +void BdfFont::drawChar(Surface *dst, uint32 chr, const int tx, const int ty, const uint32 color) const { assert(dst != 0); // TODO: Where is the relation between the max advance being smaller or // equal to 50 and the decision of the theme designer? // asserting _data.maxAdvance <= 50: let the theme designer decide what looks best assert(_data.maxAdvance <= 50); - assert(dst->format.bytesPerPixel == 1 || dst->format.bytesPerPixel == 2); + assert(dst->format.bytesPerPixel == 1 || dst->format.bytesPerPixel == 2 || dst->format.bytesPerPixel == 4); const int idx = mapToIndex(chr); if (idx < 0) @@ -165,6 +165,8 @@ void BdfFont::drawChar(Surface *dst, byte chr, const int tx, const int ty, const drawCharIntern<byte>(ptr, dst->pitch, src, height, originalWidth, xStart, xEnd, color); else if (dst->format.bytesPerPixel == 2) drawCharIntern<uint16>(ptr, dst->pitch, src, height, originalWidth, xStart, xEnd, color); + else if (dst->format.bytesPerPixel == 4) + drawCharIntern<uint32>(ptr, dst->pitch, src, height, originalWidth, xStart, xEnd, color); } namespace { diff --git a/graphics/fonts/bdf.h b/graphics/fonts/bdf.h index b0166a2095..842e54f851 100644 --- a/graphics/fonts/bdf.h +++ b/graphics/fonts/bdf.h @@ -61,14 +61,14 @@ public: virtual int getFontHeight() const; virtual int getMaxCharWidth() const; - virtual int getCharWidth(byte chr) const; - virtual void drawChar(Surface *dst, byte chr, int x, int y, uint32 color) const; + virtual int getCharWidth(uint32 chr) const; + virtual void drawChar(Surface *dst, uint32 chr, int x, int y, uint32 color) const; static BdfFont *loadFont(Common::SeekableReadStream &stream); static bool cacheFontData(const BdfFont &font, const Common::String &filename); static BdfFont *loadFromCache(Common::SeekableReadStream &stream); private: - int mapToIndex(byte ch) const; + int mapToIndex(uint32 ch) const; const BdfFontData _data; const DisposeAfterUse::Flag _dispose; diff --git a/graphics/fonts/ttf.cpp b/graphics/fonts/ttf.cpp index 2b1dca1eae..93f119f028 100644 --- a/graphics/fonts/ttf.cpp +++ b/graphics/fonts/ttf.cpp @@ -107,11 +107,11 @@ public: virtual int getMaxCharWidth() const; - virtual int getCharWidth(byte chr) const; + virtual int getCharWidth(uint32 chr) const; - virtual int getKerningOffset(byte left, byte right) const; + virtual int getKerningOffset(uint32 left, uint32 right) const; - virtual void drawChar(Surface *dst, byte chr, int x, int y, uint32 color) const; + virtual void drawChar(Surface *dst, uint32 chr, int x, int y, uint32 color) const; private: bool _initialized; FT_Face _face; @@ -126,13 +126,14 @@ private: Surface image; int xOffset, yOffset; int advance; + FT_UInt slot; }; - bool cacheGlyph(Glyph &glyph, FT_UInt &slot, uint chr); - typedef Common::HashMap<byte, Glyph> GlyphCache; - GlyphCache _glyphs; - - FT_UInt _glyphSlots[256]; + bool cacheGlyph(Glyph &glyph, uint32 chr) const; + typedef Common::HashMap<uint32, Glyph> GlyphCache; + mutable GlyphCache _glyphs; + bool _allowLateCaching; + void assureCached(uint32 chr) const; bool _monochrome; bool _hasKerning; @@ -140,7 +141,7 @@ private: TTFFont::TTFFont() : _initialized(false), _face(), _ttfFile(0), _size(0), _width(0), _height(0), _ascent(0), - _descent(0), _glyphs(), _glyphSlots(), _monochrome(false), _hasKerning(false) { + _descent(0), _glyphs(), _monochrome(false), _hasKerning(false), _allowLateCaching(false) { } TTFFont::~TTFFont() { @@ -212,19 +213,26 @@ bool TTFFont::load(Common::SeekableReadStream &stream, int size, uint dpi, bool _height = _ascent - _descent + 1; if (!mapping) { + // Allow loading of all unicode characters. + _allowLateCaching = true; + // Load all ISO-8859-1 characters. for (uint i = 0; i < 256; ++i) { - if (!cacheGlyph(_glyphs[i], _glyphSlots[i], i)) - _glyphSlots[i] = 0; + if (!cacheGlyph(_glyphs[i], i)) { + _glyphs.erase(i); + } } } else { + // We have a fixed map of characters do not load more later. + _allowLateCaching = false; + for (uint i = 0; i < 256; ++i) { const uint32 unicode = mapping[i] & 0x7FFFFFFF; const bool isRequired = (mapping[i] & 0x80000000) != 0; // Check whether loading an important glyph fails and error out if // that is the case. - if (!cacheGlyph(_glyphs[i], _glyphSlots[i], unicode)) { - _glyphSlots[i] = 0; + if (!cacheGlyph(_glyphs[i], unicode)) { + _glyphs.erase(i); if (isRequired) return false; } @@ -243,7 +251,8 @@ int TTFFont::getMaxCharWidth() const { return _width; } -int TTFFont::getCharWidth(byte chr) const { +int TTFFont::getCharWidth(uint32 chr) const { + assureCached(chr); GlyphCache::const_iterator glyphEntry = _glyphs.find(chr); if (glyphEntry == _glyphs.end()) return 0; @@ -251,12 +260,29 @@ int TTFFont::getCharWidth(byte chr) const { return glyphEntry->_value.advance; } -int TTFFont::getKerningOffset(byte left, byte right) const { +int TTFFont::getKerningOffset(uint32 left, uint32 right) const { if (!_hasKerning) return 0; - FT_UInt leftGlyph = _glyphSlots[left]; - FT_UInt rightGlyph = _glyphSlots[right]; + assureCached(left); + assureCached(right); + + FT_UInt leftGlyph, rightGlyph; + GlyphCache::const_iterator glyphEntry; + + glyphEntry = _glyphs.find(left); + if (glyphEntry != _glyphs.end()) { + leftGlyph = glyphEntry->_value.slot; + } else { + return 0; + } + + glyphEntry = _glyphs.find(right); + if (glyphEntry != _glyphs.end()) { + rightGlyph = glyphEntry->_value.slot; + } else { + return 0; + } if (!leftGlyph || !rightGlyph) return 0; @@ -304,7 +330,8 @@ void renderGlyph(uint8 *dstPos, const int dstPitch, const uint8 *srcPos, const i } // End of anonymous namespace -void TTFFont::drawChar(Surface *dst, byte chr, int x, int y, uint32 color) const { +void TTFFont::drawChar(Surface *dst, uint32 chr, int x, int y, uint32 color) const { + assureCached(chr); GlyphCache::const_iterator glyphEntry = _glyphs.find(chr); if (glyphEntry == _glyphs.end()) return; @@ -322,7 +349,7 @@ void TTFFont::drawChar(Surface *dst, byte chr, int x, int y, uint32 color) const int w = glyph.image.w; int h = glyph.image.h; - const uint8 *srcPos = (const uint8 *)glyph.image.getBasePtr(0, 0); + const uint8 *srcPos = (const uint8 *)glyph.image.getPixels(); // Make sure we are not drawing outside the screen bounds if (x < 0) { @@ -376,11 +403,13 @@ void TTFFont::drawChar(Surface *dst, byte chr, int x, int y, uint32 color) const } } -bool TTFFont::cacheGlyph(Glyph &glyph, FT_UInt &slot, uint chr) { - slot = FT_Get_Char_Index(_face, chr); +bool TTFFont::cacheGlyph(Glyph &glyph, uint32 chr) const { + FT_UInt slot = FT_Get_Char_Index(_face, chr); if (!slot) return false; + glyph.slot = slot; + // We use the light target and render mode to improve the looks of the // glyphs. It is most noticable in FreeSansBold.ttf, where otherwise the // 't' glyph looks like it is cut off on the right side. @@ -422,7 +451,7 @@ bool TTFFont::cacheGlyph(Glyph &glyph, FT_UInt &slot, uint chr) { srcPitch = -srcPitch; } - uint8 *dst = (uint8 *)glyph.image.getBasePtr(0, 0); + uint8 *dst = (uint8 *)glyph.image.getPixels(); memset(dst, 0, glyph.image.h * glyph.image.pitch); switch (bitmap.pixel_mode) { @@ -456,12 +485,24 @@ bool TTFFont::cacheGlyph(Glyph &glyph, FT_UInt &slot, uint chr) { default: warning("TTFFont::cacheGlyph: Unsupported pixel mode %d", bitmap.pixel_mode); + glyph.image.free(); return false; } return true; } +void TTFFont::assureCached(uint32 chr) const { + if (!chr || !_allowLateCaching || _glyphs.contains(chr)) { + return; + } + + Glyph newGlyph; + if (cacheGlyph(newGlyph, chr)) { + _glyphs[chr] = newGlyph; + } +} + Font *loadTTFFont(Common::SeekableReadStream &stream, int size, uint dpi, bool monochrome, const uint32 *mapping) { TTFFont *font = new TTFFont(); diff --git a/graphics/fonts/winfont.cpp b/graphics/fonts/winfont.cpp index 3bad92236d..c0ebab19ba 100644 --- a/graphics/fonts/winfont.cpp +++ b/graphics/fonts/winfont.cpp @@ -200,7 +200,7 @@ char WinFont::indexToCharacter(uint16 index) const { return index + _firstChar; } -uint16 WinFont::characterToIndex(byte character) const { +uint16 WinFont::characterToIndex(uint32 character) const { // Go to the default character if we didn't find a mapping if (character < _firstChar || character > _lastChar) character = _defaultChar; @@ -208,7 +208,7 @@ uint16 WinFont::characterToIndex(byte character) const { return character - _firstChar; } -int WinFont::getCharWidth(byte chr) const { +int WinFont::getCharWidth(uint32 chr) const { return _glyphs[characterToIndex(chr)].charWidth; } @@ -324,7 +324,7 @@ bool WinFont::loadFromFNT(Common::SeekableReadStream &stream) { return true; } -void WinFont::drawChar(Surface *dst, byte chr, int x, int y, uint32 color) const { +void WinFont::drawChar(Surface *dst, uint32 chr, int x, int y, uint32 color) const { assert(dst); assert(dst->format.bytesPerPixel == 1 || dst->format.bytesPerPixel == 2 || dst->format.bytesPerPixel == 4); assert(_glyphs); diff --git a/graphics/fonts/winfont.h b/graphics/fonts/winfont.h index 4382d7ed6b..2c7ba07b41 100644 --- a/graphics/fonts/winfont.h +++ b/graphics/fonts/winfont.h @@ -62,8 +62,8 @@ public: // Font API int getFontHeight() const { return _pixHeight; } int getMaxCharWidth() const { return _maxWidth; } - int getCharWidth(byte chr) const; - void drawChar(Surface *dst, byte chr, int x, int y, uint32 color) const; + int getCharWidth(uint32 chr) const; + void drawChar(Surface *dst, uint32 chr, int x, int y, uint32 color) const; private: bool loadFromPE(const Common::String &fileName, const WinFontDirEntry &dirEntry); @@ -72,7 +72,7 @@ private: uint32 getFontIndex(Common::SeekableReadStream &stream, const WinFontDirEntry &dirEntry); bool loadFromFNT(Common::SeekableReadStream &stream); char indexToCharacter(uint16 index) const; - uint16 characterToIndex(byte character) const; + uint16 characterToIndex(uint32 character) const; uint16 _pixHeight; uint16 _maxWidth; diff --git a/graphics/scaler/thumbnail_intern.cpp b/graphics/scaler/thumbnail_intern.cpp index 88f3cc2077..c30fc3b6fd 100644 --- a/graphics/scaler/thumbnail_intern.cpp +++ b/graphics/scaler/thumbnail_intern.cpp @@ -42,8 +42,10 @@ uint16 quadBlockInterpolate(const uint8 *src, uint32 srcPitch) { template<int bitFormat> void createThumbnail_2(const uint8 *src, uint32 srcPitch, uint8 *dstPtr, uint32 dstPitch, int width, int height) { - assert(width % 2 == 0); - assert(height % 2 == 0); + // Make sure the width and height is a multiple of 2. + width &= ~1; + height &= ~1; + for (int y = 0; y < height; y += 2) { for (int x = 0; x < width; x += 2, dstPtr += 2) { *((uint16 *)dstPtr) = quadBlockInterpolate<bitFormat>(src + 2 * x, srcPitch); @@ -55,8 +57,10 @@ void createThumbnail_2(const uint8 *src, uint32 srcPitch, uint8 *dstPtr, uint32 template<int bitFormat> void createThumbnail_4(const uint8 *src, uint32 srcPitch, uint8 *dstPtr, uint32 dstPitch, int width, int height) { - assert(width % 4 == 0); - assert(height % 4 == 0); + // Make sure the width and height is a multiple of 4 + width &= ~3; + height &= ~3; + for (int y = 0; y < height; y += 4) { for (int x = 0; x < width; x += 4, dstPtr += 2) { uint16 upleft = quadBlockInterpolate<bitFormat>(src + 2 * x, srcPitch); @@ -71,17 +75,87 @@ void createThumbnail_4(const uint8 *src, uint32 srcPitch, uint8 *dstPtr, uint32 } } -static void createThumbnail(const uint8 *src, uint32 srcPitch, uint8 *dstPtr, uint32 dstPitch, int width, int height) { - // only 1/2 and 1/4 downscale supported - if (width != 320 && width != 640) - return; +static void scaleThumbnail(Graphics::Surface &in, Graphics::Surface &out) { + while (in.w / out.w >= 4 || in.h / out.h >= 4) { + createThumbnail_4<565>((const uint8 *)in.getPixels(), in.pitch, (uint8 *)in.getPixels(), in.pitch, in.w, in.h); + in.w /= 4; + in.h /= 4; + } + + while (in.w / out.w >= 2 || in.h / out.h >= 2) { + createThumbnail_2<565>((const uint8 *)in.getPixels(), in.pitch, (uint8 *)in.getPixels(), in.pitch, in.w, in.h); + in.w /= 2; + in.h /= 2; + } + + if ((in.w == out.w && in.h < out.h) || (in.w < out.w && in.h == out.h)) { + // In this case we simply center the input surface in the output + uint8 *dst = (uint8 *)out.getBasePtr((out.w - in.w) / 2, (out.h - in.h) / 2); + const uint8 *src = (const uint8 *)in.getPixels(); - int downScaleMode = (width == 320) ? 2 : 4; + for (int y = 0; y < in.h; ++y) { + memcpy(dst, src, in.w * in.format.bytesPerPixel); + src += in.pitch; + dst += out.pitch; + } + } else { + // Assure the aspect of the scaled image still matches the original. + int targetWidth = out.w, targetHeight = out.h; - if (downScaleMode == 2) { - createThumbnail_2<565>(src, srcPitch, dstPtr, dstPitch, width, height); - } else if (downScaleMode == 4) { - createThumbnail_4<565>(src, srcPitch, dstPtr, dstPitch, width, height); + const float inputAspect = (float)in.w / in.h; + const float outputAspect = (float)out.w / out.h; + + if (inputAspect > outputAspect) { + targetHeight = int(targetWidth / inputAspect); + } else if (inputAspect < outputAspect) { + targetWidth = int(targetHeight * inputAspect); + } + + // Make sure we are still in the bounds of the output + assert(targetWidth <= out.w); + assert(targetHeight <= out.h); + + // Center the image on the output surface + byte *dst = (byte *)out.getBasePtr((out.w - targetWidth) / 2, (out.h - targetHeight) / 2); + const uint dstLineIncrease = out.pitch - targetWidth * out.format.bytesPerPixel; + + const float scaleFactorX = (float)targetWidth / in.w; + const float scaleFactorY = (float)targetHeight / in.h; + + for (int y = 0; y < targetHeight; ++y) { + const float yFrac = (y / scaleFactorY); + const int y1 = (int)yFrac; + const int y2 = (y1 + 1 < in.h) ? (y1 + 1) : (in.h - 1); + + for (int x = 0; x < targetWidth; ++x) { + const float xFrac = (x / scaleFactorX); + const int x1 = (int)xFrac; + const int x2 = (x1 + 1 < in.w) ? (x1 + 1) : (in.w - 1); + + // Look up colors at the points + uint8 p1R, p1G, p1B; + Graphics::colorToRGB<Graphics::ColorMasks<565> >(READ_UINT16(in.getBasePtr(x1, y1)), p1R, p1G, p1B); + uint8 p2R, p2G, p2B; + Graphics::colorToRGB<Graphics::ColorMasks<565> >(READ_UINT16(in.getBasePtr(x2, y1)), p2R, p2G, p2B); + uint8 p3R, p3G, p3B; + Graphics::colorToRGB<Graphics::ColorMasks<565> >(READ_UINT16(in.getBasePtr(x1, y2)), p3R, p3G, p3B); + uint8 p4R, p4G, p4B; + Graphics::colorToRGB<Graphics::ColorMasks<565> >(READ_UINT16(in.getBasePtr(x2, y2)), p4R, p4G, p4B); + + const float xDiff = xFrac - x1; + const float yDiff = yFrac - y1; + + uint8 pR = (uint8)((1 - yDiff) * ((1 - xDiff) * p1R + xDiff * p2R) + yDiff * ((1 - xDiff) * p3R + xDiff * p4R)); + uint8 pG = (uint8)((1 - yDiff) * ((1 - xDiff) * p1G + xDiff * p2G) + yDiff * ((1 - xDiff) * p3G + xDiff * p4G)); + uint8 pB = (uint8)((1 - yDiff) * ((1 - xDiff) * p1B + xDiff * p2B) + yDiff * ((1 - xDiff) * p3B + xDiff * p4B)); + + WRITE_UINT16(dst, Graphics::RGBToColor<Graphics::ColorMasks<565> >(pR, pG, pB)); + dst += 2; + } + + // Move to the next line + dst = (byte *)dst + dstLineIncrease; + } } } @@ -90,7 +164,7 @@ static void createThumbnail(const uint8 *src, uint32 srcPitch, uint8 *dstPtr, ui * Copies the current screen contents to a new surface, using RGB565 format. * WARNING: surf->free() must be called by the user to avoid leaking. * - * @param surf the surface to store the data in it + * @param surf the surface to store the data in it */ static bool grabScreen565(Graphics::Surface *surf) { Graphics::Surface *screen = g_system->lockScreen(); @@ -98,7 +172,7 @@ static bool grabScreen565(Graphics::Surface *surf) { return false; assert(screen->format.bytesPerPixel == 1 || screen->format.bytesPerPixel == 2); - assert(screen->pixels != 0); + assert(screen->getPixels() != 0); Graphics::PixelFormat screenFormat = g_system->getScreenFormat(); @@ -116,15 +190,16 @@ static bool grabScreen565(Graphics::Surface *surf) { byte r = 0, g = 0, b = 0; if (screenFormat.bytesPerPixel == 1) { - r = palette[((uint8 *)screen->pixels)[y * screen->pitch + x] * 3]; - g = palette[((uint8 *)screen->pixels)[y * screen->pitch + x] * 3 + 1]; - b = palette[((uint8 *)screen->pixels)[y * screen->pitch + x] * 3 + 2]; + uint8 pixel = *(uint8 *)screen->getBasePtr(x, y); + r = palette[pixel * 3 + 0]; + g = palette[pixel * 3 + 1]; + b = palette[pixel * 3 + 2]; } else if (screenFormat.bytesPerPixel == 2) { uint16 col = READ_UINT16(screen->getBasePtr(x, y)); screenFormat.colorToRGB(col, r, g, b); } - ((uint16 *)surf->pixels)[y * surf->w + x] = Graphics::RGBToColor<Graphics::ColorMasks<565> >(r, g, b); + *((uint16 *)surf->getBasePtr(x, y)) = Graphics::RGBToColor<Graphics::ColorMasks<565> >(r, g, b); } } @@ -135,78 +210,20 @@ static bool grabScreen565(Graphics::Surface *surf) { } static bool createThumbnail(Graphics::Surface &out, Graphics::Surface &in) { - uint16 width = in.w; - uint16 inHeight = in.h; - - if (width < 320) { - // Special case to handle MM NES (uses a screen width of 256) - width = 320; - - // center MM NES screen - Graphics::Surface newscreen; - newscreen.create(width, in.h, in.format); - - uint8 *dst = (uint8 *)newscreen.getBasePtr((320 - in.w) / 2, 0); - const uint8 *src = (const uint8 *)in.getBasePtr(0, 0); - uint16 height = in.h; - - while (height--) { - memcpy(dst, src, in.pitch); - dst += newscreen.pitch; - src += in.pitch; - } - - in.free(); - in = newscreen; - } else if (width == 720) { - // Special case to handle Hercules mode - // - // NOTE: This code is pretty SCUMM specific. - // For other games this code might cut off - // not only the menu, but also other graphics. - width = 640; - inHeight = 400; - - // cut off menu and so on.. - Graphics::Surface newscreen; - newscreen.create(width, 400, in.format); - - uint8 *dst = (uint8 *)newscreen.getBasePtr(0, (400 - 240) / 2); - const uint8 *src = (const uint8 *)in.getBasePtr(41, 28); - - for (int y = 0; y < 240; ++y) { - memcpy(dst, src, 640 * in.format.bytesPerPixel); - dst += newscreen.pitch; - src += in.pitch; - } - - in.free(); - in = newscreen; - } else if (width == 640 && inHeight == 440) { - // Special case to handle KQ6 Windows: resize the screen to 640x480, - // adding a black band in the bottom. - inHeight = 480; - - Graphics::Surface newscreen; - newscreen.create(width, 480, in.format); - - memcpy(newscreen.getBasePtr(0, 0), in.getBasePtr(0, 0), width * 440 * in.format.bytesPerPixel); - - in.free(); - in = newscreen; + int height; + if ((in.w == 320 && in.h == 200) || (in.w == 640 && in.h == 400)) { + height = kThumbnailHeight1; + } else { + height = kThumbnailHeight2; } - uint16 newHeight = !(inHeight % 240) ? kThumbnailHeight2 : kThumbnailHeight1; - - out.create(kThumbnailWidth, newHeight, Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0)); - createThumbnail((const uint8 *)in.pixels, width * sizeof(uint16), (uint8 *)out.pixels, out.pitch, width, inHeight); - + out.create(kThumbnailWidth, height, Graphics::PixelFormat(2, 5, 6, 5, 0, 11, 5, 0, 0)); + scaleThumbnail(in, out); in.free(); - return true; } -bool createThumbnailFromScreen(Graphics::Surface* surf) { +bool createThumbnailFromScreen(Graphics::Surface *surf) { assert(surf); Graphics::Surface screen; @@ -230,9 +247,37 @@ bool createThumbnail(Graphics::Surface *surf, const uint8 *pixels, int w, int h, g = palette[pixels[y * w + x] * 3 + 1]; b = palette[pixels[y * w + x] * 3 + 2]; - ((uint16 *)screen.pixels)[y * screen.w + x] = Graphics::RGBToColor<Graphics::ColorMasks<565> >(r, g, b); + *((uint16 *)screen.getBasePtr(x, y)) = Graphics::RGBToColor<Graphics::ColorMasks<565> >(r, g, b); } } return createThumbnail(*surf, screen); } + +// this is somewhat awkward, but createScreenShot should logically be in graphics, +// but moving other functions in this file into that namespace breaks several engines +namespace Graphics { +bool createScreenShot(Graphics::Surface &surf) { + Graphics::PixelFormat screenFormat = g_system->getScreenFormat(); + //convert surface to 2 bytes pixel format to avoid problems with palette saving and loading + if ((screenFormat.bytesPerPixel == 1) || (screenFormat.bytesPerPixel == 2)) { + return grabScreen565(&surf); + } else { + Graphics::Surface *screen = g_system->lockScreen(); + if (!screen) { + return false; + } + surf.create(screen->w, screen->h, Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0)); + for (uint y = 0; y < screen->h; ++y) { + for (uint x = 0; x < screen->w; ++x) { + byte r = 0, g = 0, b = 0, a = 0; + uint32 col = READ_UINT32(screen->getBasePtr(x, y)); + screenFormat.colorToARGB(col, a, r, g, b); + *((uint32 *)surf.getBasePtr(x, y)) = Graphics::ARGBToColor<Graphics::ColorMasks<8888> >(a, r, g, b); + } + } + g_system->unlockScreen(); + return true; + } +} +} // End of namespace Graphics diff --git a/graphics/surface.cpp b/graphics/surface.cpp index 41ae8dcebb..777c1058fb 100644 --- a/graphics/surface.cpp +++ b/graphics/surface.cpp @@ -82,9 +82,79 @@ void Surface::free() { format = PixelFormat(); } +void Surface::init(uint16 width, uint16 height, uint16 newPitch, void *newPixels, const PixelFormat &f) { + w = width; + h = height; + pitch = newPitch; + pixels = newPixels; + format = f; +} + void Surface::copyFrom(const Surface &surf) { create(surf.w, surf.h, surf.format); - memcpy(pixels, surf.pixels, h * pitch); + if (surf.pitch == pitch) { + memcpy(pixels, surf.pixels, h * pitch); + } else { + const byte *src = (const byte *)surf.pixels; + byte *dst = (byte *)pixels; + for (int y = h; y > 0; --y) { + memcpy(dst, src, w * format.bytesPerPixel); + src += surf.pitch; + dst += pitch; + } + } +} + +Surface Surface::getSubArea(const Common::Rect &area) { + Common::Rect effectiveArea(area); + effectiveArea.clip(w, h); + + Surface subSurface; + subSurface.w = effectiveArea.width(); + subSurface.h = effectiveArea.height(); + subSurface.pitch = pitch; + subSurface.pixels = getBasePtr(area.left, area.top); + subSurface.format = format; + return subSurface; +} + +const Surface Surface::getSubArea(const Common::Rect &area) const { + Common::Rect effectiveArea(area); + effectiveArea.clip(w, h); + + Surface subSurface; + subSurface.w = effectiveArea.width(); + subSurface.h = effectiveArea.height(); + subSurface.pitch = pitch; + // We need to cast the const away here because a Surface always has a + // pointer to modifiable pixel data. + subSurface.pixels = const_cast<void *>(getBasePtr(area.left, area.top)); + subSurface.format = format; + return subSurface; +} + +void Surface::copyRectToSurface(const void *buffer, int srcPitch, int destX, int destY, int width, int height) { + assert(buffer); + + assert(destX >= 0 && destX < w); + assert(destY >= 0 && destY < h); + assert(height > 0 && destY + height <= h); + assert(width > 0 && destX + width <= w); + + // Copy buffer data to internal buffer + const byte *src = (const byte *)buffer; + byte *dst = (byte *)getBasePtr(destX, destY); + for (int i = 0; i < height; i++) { + memcpy(dst, src, width * format.bytesPerPixel); + src += srcPitch; + dst += pitch; + } +} + +void Surface::copyRectToSurface(const Graphics::Surface &srcSurface, int destX, int destY, const Common::Rect subRect) { + assert(srcSurface.format == format); + + copyRectToSurface(srcSurface.getBasePtr(subRect.left, subRect.top), srcSurface.pitch, destX, destY, subRect.width(), subRect.height()); } void Surface::hLine(int x, int y, int x2, uint32 color) { diff --git a/graphics/surface.h b/graphics/surface.h index 6c9e464657..f1b2aa64ab 100644 --- a/graphics/surface.h +++ b/graphics/surface.h @@ -61,11 +61,13 @@ struct Surface { */ uint16 pitch; +protected: /** * The surface's pixel data. */ void *pixels; +public: /** * The pixel format of the surface. */ @@ -78,6 +80,33 @@ struct Surface { } /** + * Return a pointer to the pixel data. + * + * @return Pointer to the pixel data. + */ + inline const void *getPixels() const { + return pixels; + } + + /** + * Return a pointer to the pixel data. + * + * @return Pointer to the pixel data. + */ + inline void *getPixels() { + return pixels; + } + + /** + * Sets the pixel data. + * + * Note that this is a simply a setter. Be careful what you are doing! + * + * @param newPixels The new pixel data. + */ + void setPixels(void *newPixels) { pixels = newPixels; } + + /** * Return a pointer to the pixel at the specified point. * * @param x The x coordinate of the pixel. @@ -122,6 +151,20 @@ struct Surface { void free(); /** + * Set up the Surface with user specified data. + * + * Note that this simply sets the 'internal' attributes of the Surface. It + * will not take care of freeing old data via free or similar! + * + * @param width Width of the pixel data. + * @param height Height of the pixel data. + * @param pitch The pitch of the pixel data. + * @param pixels The pixel data itself. + * @param format The pixel format of the pixel data. + */ + void init(uint16 width, uint16 height, uint16 pitch, void *pixels, const PixelFormat &format); + + /** * Copy the data from another Surface. * * Note that this calls free on the current surface, to assure it being @@ -135,10 +178,66 @@ struct Surface { void copyFrom(const Surface &surf); /** + * Creates a Surface which represents a sub-area of this Surface object. + * + * The pixel (0, 0) of the returned Surface will be the same as Pixel + * (area.x, area.y) of this Surface. Changes to any of the Surface objects + * will change the shared pixel data. + * + * Note that the Surface returned is only valid as long as this Surface + * object is still alive (i.e. its pixel data is not destroyed or + * reallocated). Do *never* try to free the returned Surface. + * + * @param area The area which should be represented. Note that the area + * will get clipped in case it does not fit! + */ + Surface getSubArea(const Common::Rect &area); + + /** + * Creates a Surface which represents a sub-area of this Surface object. + * + * The pixel (0, 0) of the returned Surface will be the same as Pixel + * (area.x, area.y) of this Surface. + * + * Note that the Surface returned is only valid as long as this Surface + * object is still alive (i.e. its pixel data is not destroyed or + * reallocated). Do *never* try to free the returned Surface. + * + * @param area The area which should be represented. Note that the area + * will get clipped in case it does not fit! + */ + const Surface getSubArea(const Common::Rect &area) const; + + /** + * Copies a bitmap to the Surface internal buffer. The pixel format + * of buffer must match the pixel format of the Surface. + * + * @param buffer The buffer containing the graphics data source + * @param pitch The pitch of the buffer (number of bytes in a scanline) + * @param destX The x coordinate of the destination rectangle + * @param destY The y coordinate of the destination rectangle + * @param width The width of the destination rectangle + * @param height The height of the destination rectangle + */ + void copyRectToSurface(const void *buffer, int srcPitch, int destX, int destY, int width, int height); + /** + * Copies a bitmap to the Surface internal buffer. The pixel format + * of buffer must match the pixel format of the Surface. + * + * @param srcSurface The source of the bitmap data + * @param destX The x coordinate of the destination rectangle + * @param destY The y coordinate of the destination rectangle + * @param subRect The subRect of surface to be blitted + * @return + */ + void copyRectToSurface(const Graphics::Surface &srcSurface, int destX, int destY, const Common::Rect subRect); + + /** * Convert the data to another pixel format. * * This works in-place. This means it will not create an additional buffer - * for the conversion process. The value of pixels might change though. + * for the conversion process. The value of 'pixels' might change though + * (that means it might realloc the pixel data). * * Note that you should only use this, when you created the Surface data via * create! Otherwise this function has undefined behavior. @@ -235,7 +334,9 @@ struct Surface { */ struct SharedPtrSurfaceDeleter { void operator()(Surface *ptr) { - ptr->free(); + if (ptr) { + ptr->free(); + } delete ptr; } }; diff --git a/graphics/thumbnail.cpp b/graphics/thumbnail.cpp index ddb377306d..e3f368dffa 100644 --- a/graphics/thumbnail.cpp +++ b/graphics/thumbnail.cpp @@ -23,6 +23,7 @@ #include "graphics/scaler.h" #include "graphics/colormasks.h" #include "common/endian.h" +#include "common/algorithm.h" #include "common/system.h" #include "common/stream.h" #include "common/textconsole.h" @@ -42,7 +43,16 @@ struct ThumbnailHeader { #define ThumbnailHeaderSize (4+4+1+2+2+(1+4+4)) -bool loadHeader(Common::SeekableReadStream &in, ThumbnailHeader &header, bool outputWarnings) { +enum HeaderState { + /// There is no header present + kHeaderNone, + /// The header present only has reliable values for version and size + kHeaderUnsupported, + /// The header is present and the version is supported + kHeaderPresent +}; + +HeaderState loadHeader(Common::SeekableReadStream &in, ThumbnailHeader &header, bool outputWarnings) { header.type = in.readUint32BE(); // We also accept the bad 'BMHT' header here, for the sake of compatibility // with some older savegames which were written incorrectly due to a bug in @@ -50,16 +60,28 @@ bool loadHeader(Common::SeekableReadStream &in, ThumbnailHeader &header, bool ou if (header.type != MKTAG('T','H','M','B') && header.type != MKTAG('B','M','H','T')) { if (outputWarnings) warning("couldn't find thumbnail header type"); - return false; + return kHeaderNone; } header.size = in.readUint32BE(); header.version = in.readByte(); + // Do a check whether any read errors had occured. If so we cannot use the + // values obtained for size and version because they might be bad. + if (in.err() || in.eos()) { + // TODO: We fake that there is no header. This is actually not quite + // correct since we found the start of the header and then things + // started to break. Right no we leave detection of this to the client. + // Since this case is caused by broken files, the client code should + // catch it anyway... If there is a nicer solution here, we should + // implement it. + return kHeaderNone; + } + if (header.version > THMB_VERSION) { if (outputWarnings) warning("trying to load a newer thumbnail version: %d instead of %d", header.version, THMB_VERSION); - return false; + return kHeaderUnsupported; } header.width = in.readUint16BE(); @@ -81,7 +103,15 @@ bool loadHeader(Common::SeekableReadStream &in, ThumbnailHeader &header, bool ou header.format = createPixelFormat<565>(); } - return true; + if (in.err() || in.eos()) { + // When we reached this point we know that at least the size and + // version field was loaded successfully, thus we tell this header + // is not supported and silently hope that the client code is + // prepared to handle read errors. + return kHeaderUnsupported; + } else { + return kHeaderPresent; + } } } // end of anonymous namespace @@ -89,7 +119,12 @@ bool checkThumbnailHeader(Common::SeekableReadStream &in) { uint32 position = in.pos(); ThumbnailHeader header; - bool hasHeader = loadHeader(in, header, false); + // TODO: It is not clear whether this is the best semantics. Now + // checkThumbnailHeader will return true even when the thumbnail header + // found is actually not usable. However, most engines seem to use this + // to detect the presence of any header and if there is none it wont even + // try to skip it. Thus, this looks like the best solution for now... + bool hasHeader = (loadHeader(in, header, false) != kHeaderNone); in.seek(position, SEEK_SET); @@ -100,7 +135,9 @@ bool skipThumbnail(Common::SeekableReadStream &in) { uint32 position = in.pos(); ThumbnailHeader header; - if (!loadHeader(in, header, false)) { + // We can skip unsupported and supported headers. So we only seek back + // to the old position in case there is no header at all. + if (loadHeader(in, header, false) == kHeaderNone) { in.seek(position, SEEK_SET); return false; } @@ -110,10 +147,23 @@ bool skipThumbnail(Common::SeekableReadStream &in) { } Graphics::Surface *loadThumbnail(Common::SeekableReadStream &in) { + const uint32 position = in.pos(); ThumbnailHeader header; - - if (!loadHeader(in, header, true)) + HeaderState headerState = loadHeader(in, header, true); + + // Try to handle unsupported/broken headers gracefully. If there is no + // header at all, we seek back and return at this point. If there is an + // unsupported/broken header, we skip the actual data and return. The + // downside is that we might reset the end of stream flag with this and + // the client code would not be able to notice a read past the end of the + // stream at this point then. + if (headerState == kHeaderNone) { + in.seek(position, SEEK_SET); + return 0; + } else if (headerState == kHeaderUnsupported) { + in.seek(header.size - (in.pos() - position), SEEK_CUR); return 0; + } if (header.format.bytesPerPixel != 2 && header.format.bytesPerPixel != 4) { warning("trying to load thumbnail with unsupported bit depth %d", header.format.bytesPerPixel); @@ -143,7 +193,6 @@ Graphics::Surface *loadThumbnail(Common::SeekableReadStream &in) { assert(0); } } - return to; } @@ -216,4 +265,55 @@ bool saveThumbnail(Common::WriteStream &out, const Graphics::Surface &thumb) { return true; } + +/** + * Returns an array indicating which pixels of a source image horizontally or vertically get + * included in a scaled image + */ +int *scaleLine(int size, int srcSize) { + int scale = 100 * size / srcSize; + assert(scale > 0); + int *v = new int[size]; + Common::fill(v, &v[size], 0); + + int distCtr = 0; + int *destP = v; + for (int distIndex = 0; distIndex < srcSize; ++distIndex) { + distCtr += scale; + while (distCtr >= 100) { + assert(destP < &v[size]); + *destP++ = distIndex; + distCtr -= 100; + } + } + + return v; +} + +Graphics::Surface *scale(const Graphics::Surface &srcImage, int xSize, int ySize) { + Graphics::Surface *s = new Graphics::Surface(); + s->create(xSize, ySize, srcImage.format); + + int *horizUsage = scaleLine(xSize, srcImage.w); + int *vertUsage = scaleLine(ySize, srcImage.h); + + // Loop to create scaled version + for (int yp = 0; yp < ySize; ++yp) { + const byte *srcP = (const byte *)srcImage.getBasePtr(0, vertUsage[yp]); + byte *destP = (byte *)s->getBasePtr(0, yp); + + for (int xp = 0; xp < xSize; ++xp) { + const byte *tempSrcP = srcP + (horizUsage[xp] * srcImage.format.bytesPerPixel); + for (int byteCtr = 0; byteCtr < srcImage.format.bytesPerPixel; ++byteCtr) { + *destP++ = *tempSrcP++; + } + } + } + + // Delete arrays and return surface + delete[] horizUsage; + delete[] vertUsage; + return s; +} + } // End of namespace Graphics diff --git a/graphics/thumbnail.h b/graphics/thumbnail.h index 45a0fdbf07..c857809c91 100644 --- a/graphics/thumbnail.h +++ b/graphics/thumbnail.h @@ -64,6 +64,24 @@ bool saveThumbnail(Common::WriteStream &out); */ bool saveThumbnail(Common::WriteStream &out, const Graphics::Surface &thumb); +/** + * Grabs framebuffer into surface + * + * @param surf a surface + * @return false if a error occurred + */ +bool createScreenShot(Graphics::Surface &surf); + +/** + * Scales a passed surface, creating a new surface with the result + * @param srcImage Source image to scale + * @param xSize New surface width + * @param ySize New surface height + * @remarks Caller is responsible for freeing the returned surface + */ +Graphics::Surface *scale(const Graphics::Surface &srcImage, int xSize, int ySize); + + } // End of namespace Graphics #endif diff --git a/graphics/yuv_to_rgb.cpp b/graphics/yuv_to_rgb.cpp index 6043315a13..2a485fa664 100644 --- a/graphics/yuv_to_rgb.cpp +++ b/graphics/yuv_to_rgb.cpp @@ -229,7 +229,7 @@ void convertYUV444ToRGB(byte *dstPtr, int dstPitch, const YUVToRGBLookup *lookup void YUVToRGBManager::convert444(Graphics::Surface *dst, YUVToRGBManager::LuminanceScale scale, const byte *ySrc, const byte *uSrc, const byte *vSrc, int yWidth, int yHeight, int yPitch, int uvPitch) { // Sanity checks - assert(dst && dst->pixels); + assert(dst && dst->getPixels()); assert(dst->format.bytesPerPixel == 2 || dst->format.bytesPerPixel == 4); assert(ySrc && uSrc && vSrc); @@ -237,9 +237,9 @@ void YUVToRGBManager::convert444(Graphics::Surface *dst, YUVToRGBManager::Lumina // Use a templated function to avoid an if check on every pixel if (dst->format.bytesPerPixel == 2) - convertYUV444ToRGB<uint16>((byte *)dst->pixels, dst->pitch, lookup, _colorTab, ySrc, uSrc, vSrc, yWidth, yHeight, yPitch, uvPitch); + convertYUV444ToRGB<uint16>((byte *)dst->getPixels(), dst->pitch, lookup, _colorTab, ySrc, uSrc, vSrc, yWidth, yHeight, yPitch, uvPitch); else - convertYUV444ToRGB<uint32>((byte *)dst->pixels, dst->pitch, lookup, _colorTab, ySrc, uSrc, vSrc, yWidth, yHeight, yPitch, uvPitch); + convertYUV444ToRGB<uint32>((byte *)dst->getPixels(), dst->pitch, lookup, _colorTab, ySrc, uSrc, vSrc, yWidth, yHeight, yPitch, uvPitch); } template<typename PixelInt> @@ -283,7 +283,7 @@ void convertYUV420ToRGB(byte *dstPtr, int dstPitch, const YUVToRGBLookup *lookup void YUVToRGBManager::convert420(Graphics::Surface *dst, YUVToRGBManager::LuminanceScale scale, const byte *ySrc, const byte *uSrc, const byte *vSrc, int yWidth, int yHeight, int yPitch, int uvPitch) { // Sanity checks - assert(dst && dst->pixels); + assert(dst && dst->getPixels()); assert(dst->format.bytesPerPixel == 2 || dst->format.bytesPerPixel == 4); assert(ySrc && uSrc && vSrc); assert((yWidth & 1) == 0); @@ -293,9 +293,9 @@ void YUVToRGBManager::convert420(Graphics::Surface *dst, YUVToRGBManager::Lumina // Use a templated function to avoid an if check on every pixel if (dst->format.bytesPerPixel == 2) - convertYUV420ToRGB<uint16>((byte *)dst->pixels, dst->pitch, lookup, _colorTab, ySrc, uSrc, vSrc, yWidth, yHeight, yPitch, uvPitch); + convertYUV420ToRGB<uint16>((byte *)dst->getPixels(), dst->pitch, lookup, _colorTab, ySrc, uSrc, vSrc, yWidth, yHeight, yPitch, uvPitch); else - convertYUV420ToRGB<uint32>((byte *)dst->pixels, dst->pitch, lookup, _colorTab, ySrc, uSrc, vSrc, yWidth, yHeight, yPitch, uvPitch); + convertYUV420ToRGB<uint32>((byte *)dst->getPixels(), dst->pitch, lookup, _colorTab, ySrc, uSrc, vSrc, yWidth, yHeight, yPitch, uvPitch); } #define READ_QUAD(ptr, prefix) \ @@ -368,7 +368,7 @@ void convertYUV410ToRGB(byte *dstPtr, int dstPitch, const YUVToRGBLookup *lookup void YUVToRGBManager::convert410(Graphics::Surface *dst, YUVToRGBManager::LuminanceScale scale, const byte *ySrc, const byte *uSrc, const byte *vSrc, int yWidth, int yHeight, int yPitch, int uvPitch) { // Sanity checks - assert(dst && dst->pixels); + assert(dst && dst->getPixels()); assert(dst->format.bytesPerPixel == 2 || dst->format.bytesPerPixel == 4); assert(ySrc && uSrc && vSrc); assert((yWidth & 3) == 0); @@ -378,9 +378,9 @@ void YUVToRGBManager::convert410(Graphics::Surface *dst, YUVToRGBManager::Lumina // Use a templated function to avoid an if check on every pixel if (dst->format.bytesPerPixel == 2) - convertYUV410ToRGB<uint16>((byte *)dst->pixels, dst->pitch, lookup, _colorTab, ySrc, uSrc, vSrc, yWidth, yHeight, yPitch, uvPitch); + convertYUV410ToRGB<uint16>((byte *)dst->getPixels(), dst->pitch, lookup, _colorTab, ySrc, uSrc, vSrc, yWidth, yHeight, yPitch, uvPitch); else - convertYUV410ToRGB<uint32>((byte *)dst->pixels, dst->pitch, lookup, _colorTab, ySrc, uSrc, vSrc, yWidth, yHeight, yPitch, uvPitch); + convertYUV410ToRGB<uint32>((byte *)dst->getPixels(), dst->pitch, lookup, _colorTab, ySrc, uSrc, vSrc, yWidth, yHeight, yPitch, uvPitch); } } // End of namespace Graphics diff --git a/graphics/yuv_to_rgb.h b/graphics/yuv_to_rgb.h index f785422c5a..a1e61ec705 100644 --- a/graphics/yuv_to_rgb.h +++ b/graphics/yuv_to_rgb.h @@ -22,10 +22,17 @@ /** * @file - * YUV to RGB conversion used in engines: - * - mohawk - * - scumm (he) - * - sword25 + * YUV to RGB conversion. + * + * Used in graphics: + * - JPEGDecoder + * + * Used in video: + * - BinkDecoder + * - Indeo3Decoder + * - PSXStreamDecoder + * - TheoraDecoder + * - SVQ1Decoder */ #ifndef GRAPHICS_YUV_TO_RGB_H |