diff options
Diffstat (limited to 'graphics')
33 files changed, 6915 insertions, 73 deletions
diff --git a/graphics/VectorRenderer.cpp b/graphics/VectorRenderer.cpp index f426dd8c41..73dc6309b2 100644 --- a/graphics/VectorRenderer.cpp +++ b/graphics/VectorRenderer.cpp @@ -55,7 +55,34 @@ void VectorRenderer::drawStep(const Common::Rect &area, const DrawStep &step, ui _dynamicData = extra; - (this->*(step.drawingCall))(area, step); + Common::Rect noClip = Common::Rect(0, 0, 0, 0); + (this->*(step.drawingCall))(area, step, noClip); +} + +void VectorRenderer::drawStepClip(const Common::Rect &area, const Common::Rect &clip, const DrawStep &step, uint32 extra) { + + if (step.bgColor.set) + setBgColor(step.bgColor.r, step.bgColor.g, step.bgColor.b); + + if (step.fgColor.set) + setFgColor(step.fgColor.r, step.fgColor.g, step.fgColor.b); + + if (step.bevelColor.set) + setBevelColor(step.bevelColor.r, step.bevelColor.g, step.bevelColor.b); + + if (step.gradColor1.set && step.gradColor2.set) + setGradientColors(step.gradColor1.r, step.gradColor1.g, step.gradColor1.b, + step.gradColor2.r, step.gradColor2.g, step.gradColor2.b); + + setShadowOffset(_disableShadows ? 0 : step.shadow); + setBevel(step.bevel); + setGradientFactor(step.factor); + setStrokeWidth(step.stroke); + setFillMode((FillMode)step.fillMode); + + _dynamicData = extra; + + (this->*(step.drawingCall))(area, step, clip); } int VectorRenderer::stepGetRadius(const DrawStep &step, const Common::Rect &area) { diff --git a/graphics/VectorRenderer.h b/graphics/VectorRenderer.h index 6b657f758d..5f7b6e60d3 100644 --- a/graphics/VectorRenderer.h +++ b/graphics/VectorRenderer.h @@ -28,6 +28,7 @@ #include "common/str.h" #include "graphics/surface.h" +#include "graphics/transparent_surface.h" #include "gui/ThemeEngine.h" @@ -38,7 +39,7 @@ class VectorRenderer; struct DrawStep; -typedef void (VectorRenderer::*DrawingFunctionCallback)(const Common::Rect &, const Graphics::DrawStep &); +typedef void (VectorRenderer::*DrawingFunctionCallback)(const Common::Rect &, const Graphics::DrawStep &, const Common::Rect &); struct DrawStep { @@ -79,8 +80,11 @@ struct DrawStep { uint32 scale; /**< scale of all the coordinates in FIXED POINT with 16 bits mantissa */ + GUI::ThemeEngine::AutoScaleMode autoscale; /**< scale alphaimage if present */ + DrawingFunctionCallback drawingCall; /**< Pointer to drawing function */ Graphics::Surface *blitSrc; + Graphics::TransparentSurface *blitAlphaSrc; }; VectorRenderer *createRenderer(int mode); @@ -142,6 +146,7 @@ public: * @param y2 Vertical (Y) coordinate for the line end */ virtual void drawLine(int x1, int y1, int x2, int y2) = 0; + virtual void drawLineClip(int x1, int y1, int x2, int y2, Common::Rect clipping) = 0; /** * Draws a circle centered at (x,y) with radius r. @@ -151,6 +156,7 @@ public: * @param r Radius of the circle. */ virtual void drawCircle(int x, int y, int r) = 0; + virtual void drawCircleClip(int x, int y, int r, Common::Rect clipping) = 0; /** * Draws a square starting at (x,y) with the given width and height. @@ -161,6 +167,7 @@ public: * @param h Height of the square */ virtual void drawSquare(int x, int y, int w, int h) = 0; + virtual void drawSquareClip(int x, int y, int w, int h, Common::Rect clipping) = 0; /** * Draws a rounded square starting at (x,y) with the given width and height. @@ -173,6 +180,7 @@ public: * @param r Radius of the corners. */ virtual void drawRoundedSquare(int x, int y, int r, int w, int h) = 0; + virtual void drawRoundedSquareClip(int x, int y, int r, int w, int h, Common::Rect clipping) = 0; /** * Draws a triangle starting at (x,y) with the given base and height. @@ -186,6 +194,7 @@ public: * @param orient Orientation of the triangle. */ virtual void drawTriangle(int x, int y, int base, int height, TriangleOrientation orient) = 0; + virtual void drawTriangleClip(int x, int y, int base, int height, TriangleOrientation orient, Common::Rect clipping) = 0; /** * Draws a beveled square like the ones in the Classic GUI themes. @@ -199,6 +208,7 @@ public: * @param bevel Amount of bevel. Must be positive. */ virtual void drawBeveledSquare(int x, int y, int w, int h, int bevel) = 0; + virtual void drawBeveledSquareClip(int x, int y, int w, int h, int bevel, Common::Rect clipping) = 0; /** * Draws a tab-like shape, specially thought for the Tab widget. @@ -212,6 +222,7 @@ public: * @param r Radius of the corners of the tab (0 for squared tabs). */ virtual void drawTab(int x, int y, int r, int w, int h) = 0; + virtual void drawTabClip(int x, int y, int r, int w, int h, Common::Rect clipping) = 0; /** @@ -222,6 +233,11 @@ public: drawLine(x + w, y, x, y + h); } + virtual void drawCrossClip(int x, int y, int w, int h, Common::Rect clipping) { + drawLineClip(x, y, x + w, y + w, clipping); + drawLineClip(x + w, y, x, y + h, clipping); + } + /** * Set the active foreground painting color for the renderer. * All the foreground drawing from then on will be done with that color, unless @@ -269,7 +285,7 @@ public: * * @param surface Pointer to a Surface object. */ - virtual void setSurface(Surface *surface) { + virtual void setSurface(TransparentSurface *surface) { _activeSurface = surface; } @@ -278,6 +294,7 @@ public: * Defaults to using the active Foreground color for filling. */ virtual void fillSurface() = 0; + virtual void fillSurfaceClip(Common::Rect clipping) = 0; /** * Clears the active surface. @@ -355,68 +372,74 @@ public: /** * DrawStep callback functions for each drawing feature */ - void drawCallback_CIRCLE(const Common::Rect &area, const DrawStep &step) { + void drawCallback_CIRCLE(const Common::Rect &area, const DrawStep &step, const Common::Rect &clip) { uint16 x, y, w, h, radius; radius = stepGetRadius(step, area); stepGetPositions(step, area, x, y, w, h); - drawCircle(x + radius, y + radius, radius); + drawCircleClip(x + radius, y + radius, radius, clip); } - void drawCallback_SQUARE(const Common::Rect &area, const DrawStep &step) { + void drawCallback_SQUARE(const Common::Rect &area, const DrawStep &step, const Common::Rect &clip) { uint16 x, y, w, h; stepGetPositions(step, area, x, y, w, h); - drawSquare(x, y, w, h); + drawSquareClip(x, y, w, h, clip); } - void drawCallback_LINE(const Common::Rect &area, const DrawStep &step) { + void drawCallback_LINE(const Common::Rect &area, const DrawStep &step, const Common::Rect &clip) { uint16 x, y, w, h; stepGetPositions(step, area, x, y, w, h); - drawLine(x, y, x + w, y + w); + drawLineClip(x, y, x + w, y + w, clip); } - void drawCallback_ROUNDSQ(const Common::Rect &area, const DrawStep &step) { + void drawCallback_ROUNDSQ(const Common::Rect &area, const DrawStep &step, const Common::Rect &clip) { uint16 x, y, w, h; stepGetPositions(step, area, x, y, w, h); - drawRoundedSquare(x, y, stepGetRadius(step, area), w, h); + drawRoundedSquareClip(x, y, stepGetRadius(step, area), w, h, clip); } - void drawCallback_FILLSURFACE(const Common::Rect &area, const DrawStep &step) { - fillSurface(); + void drawCallback_FILLSURFACE(const Common::Rect &area, const DrawStep &step, const Common::Rect &clip) { + fillSurfaceClip(clip); } - void drawCallback_TRIANGLE(const Common::Rect &area, const DrawStep &step) { + void drawCallback_TRIANGLE(const Common::Rect &area, const DrawStep &step, const Common::Rect &clip) { uint16 x, y, w, h; stepGetPositions(step, area, x, y, w, h); - drawTriangle(x, y, w, h, (TriangleOrientation)step.extraData); + drawTriangleClip(x, y, w, h, (TriangleOrientation)step.extraData, clip); } - void drawCallback_BEVELSQ(const Common::Rect &area, const DrawStep &step) { + void drawCallback_BEVELSQ(const Common::Rect &area, const DrawStep &step, const Common::Rect &clip) { uint16 x, y, w, h; stepGetPositions(step, area, x, y, w, h); - drawBeveledSquare(x, y, w, h, _bevel); + drawBeveledSquareClip(x, y, w, h, _bevel, clip); } - void drawCallback_TAB(const Common::Rect &area, const DrawStep &step) { + void drawCallback_TAB(const Common::Rect &area, const DrawStep &step, const Common::Rect &clip) { uint16 x, y, w, h; stepGetPositions(step, area, x, y, w, h); - drawTab(x, y, stepGetRadius(step, area), w, h); + drawTabClip(x, y, stepGetRadius(step, area), w, h, clip); } - void drawCallback_BITMAP(const Common::Rect &area, const DrawStep &step) { + void drawCallback_BITMAP(const Common::Rect &area, const DrawStep &step, const Common::Rect &clip) { uint16 x, y, w, h; stepGetPositions(step, area, x, y, w, h); - blitAlphaBitmap(step.blitSrc, Common::Rect(x, y, x + w, y + h)); + blitKeyBitmapClip(step.blitSrc, Common::Rect(x, y, x + w, y + h), clip); } - void drawCallback_CROSS(const Common::Rect &area, const DrawStep &step) { + void drawCallback_ALPHABITMAP(const Common::Rect &area, const DrawStep &step, const Common::Rect &clip) { uint16 x, y, w, h; stepGetPositions(step, area, x, y, w, h); - drawCross(x, y, w, h); + blitAlphaBitmap(step.blitAlphaSrc, Common::Rect(x, y, x + w, y + h), step.autoscale, step.xAlign, step.yAlign); //TODO } - void drawCallback_VOID(const Common::Rect &area, const DrawStep &step) {} + void drawCallback_CROSS(const Common::Rect &area, const DrawStep &step, const Common::Rect &clip) { + uint16 x, y, w, h; + stepGetPositions(step, area, x, y, w, h); + drawCrossClip(x, y, w, h, clip); + } + + void drawCallback_VOID(const Common::Rect &area, const DrawStep &step, const Common::Rect &clip) {} /** * Draws the specified draw step on the screen. @@ -426,6 +449,7 @@ public: * @param step Pointer to a DrawStep struct. */ virtual void drawStep(const Common::Rect &area, const DrawStep &step, uint32 extra = 0); + virtual void drawStepClip(const Common::Rect &area, const Common::Rect &clip, const DrawStep &step, uint32 extra = 0); /** * Copies the part of the current frame to the system overlay. @@ -466,8 +490,16 @@ public: * blitted into the active surface, at the position specified by "r". */ virtual void blitSubSurface(const Graphics::Surface *source, const Common::Rect &r) = 0; + virtual void blitSubSurfaceClip(const Graphics::Surface *source, const Common::Rect &r, const Common::Rect &clipping) = 0; + + virtual void blitKeyBitmap(const Graphics::Surface *source, const Common::Rect &r) = 0; + virtual void blitKeyBitmapClip(const Graphics::Surface *source, const Common::Rect &r, const Common::Rect &clipping) = 0; - virtual void blitAlphaBitmap(const Graphics::Surface *source, const Common::Rect &r) = 0; + virtual void blitAlphaBitmap(Graphics::TransparentSurface *source, const Common::Rect &r, + GUI::ThemeEngine::AutoScaleMode autoscale = GUI::ThemeEngine::kAutoScaleNone, + Graphics::DrawStep::VectorAlignment xAlign = Graphics::DrawStep::kVectorAlignManual, + Graphics::DrawStep::VectorAlignment yAlign = Graphics::DrawStep::kVectorAlignManual, + int alpha = 255) = 0; /** * Draws a string into the screen. Wrapper for the Graphics::Font string drawing @@ -491,7 +523,7 @@ public: virtual void applyScreenShading(GUI::ThemeEngine::ShadingStyle) = 0; protected: - Surface *_activeSurface; /**< Pointer to the surface currently being drawn */ + TransparentSurface *_activeSurface; /**< Pointer to the surface currently being drawn */ FillMode _fillMode; /**< Defines in which way (if any) are filled the drawn shapes */ ShadowFillMode _shadowFillMode; diff --git a/graphics/VectorRendererSpec.cpp b/graphics/VectorRendererSpec.cpp index 81a0c04441..9aed3301fa 100644 --- a/graphics/VectorRendererSpec.cpp +++ b/graphics/VectorRendererSpec.cpp @@ -25,6 +25,8 @@ #include "common/frac.h" #include "graphics/surface.h" +#include "graphics/transparent_surface.h" +#include "graphics/nine_patch.h" #include "graphics/colormasks.h" #include "gui/ThemeEngine.h" @@ -108,6 +110,33 @@ inline frac_t fp_sqroot(uint32 x) { BE_DRAWCIRCLE_BOTTOM(ptr3,ptr4,x,y,px,py); \ } while (0) +#define BE_DRAWCIRCLE_TOP_CLIP(ptr1,ptr2,x,y,px,py,realX1,realY1,realX2,realY2) do { \ + if (IS_IN_CLIP((realX1) + (y), (realY1) - (x))) \ + *(ptr1 + (y) - (px)) = color; \ + if (IS_IN_CLIP((realX1) + (x), (realY1) - (y))) \ + *(ptr1 + (x) - (py)) = color; \ + if (IS_IN_CLIP((realX2) - (x), (realY2) - (y))) \ + *(ptr2 - (x) - (py)) = color; \ + if (IS_IN_CLIP((realX2) - (y), (realY2) - (x))) \ + *(ptr2 - (y) - (px)) = color; \ +} while (0) + +#define BE_DRAWCIRCLE_BOTTOM_CLIP(ptr3,ptr4,x,y,px,py,realX3,realY3,realX4,realY4) do { \ + if (IS_IN_CLIP((realX3) - (y), (realY3) + (x))) \ + *(ptr3 - (y) + (px)) = color; \ + if (IS_IN_CLIP((realX3) - (x), (realY3) + (y))) \ + *(ptr3 - (x) + (py)) = color; \ + if (IS_IN_CLIP((realX4) + (x), (realY4) + (y))) \ + *(ptr4 + (x) + (py)) = color; \ + if (IS_IN_CLIP((realX4) + (y), (realY4) + (x))) \ + *(ptr4 + (y) + (px)) = color; \ +} while (0) + +#define BE_DRAWCIRCLE_CLIP(ptr1,ptr2,ptr3,ptr4,x,y,px,py,realX1,realY1,realX2,realY2,realX3,realY3,realX4,realY4) do { \ + BE_DRAWCIRCLE_TOP_CLIP(ptr1,ptr2,x,y,px,py,realX1,realY1,realX2,realY2); \ + BE_DRAWCIRCLE_BOTTOM_CLIP(ptr3,ptr4,x,y,px,py,realX3,realY3,realX4,realY4); \ +} while (0) + #define BE_DRAWCIRCLE_BCOLOR(ptr1,ptr2,ptr3,ptr4,x,y,px,py) do { \ *(ptr1 + (y) - (px)) = color1; \ *(ptr1 + (x) - (py)) = color1; \ @@ -119,6 +148,25 @@ inline frac_t fp_sqroot(uint32 x) { *(ptr4 + (y) + (px)) = color2; \ } while (0) +#define BE_DRAWCIRCLE_BCOLOR_CLIP(ptr1,ptr2,ptr3,ptr4,x,y,px,py,realX1,realY1,realX2,realY2,realX3,realY3,realX4,realY4) do { \ + if (IS_IN_CLIP((realX1) + (y), (realY1) - (x))) \ + *(ptr1 + (y) - (px)) = color1; \ + if (IS_IN_CLIP((realX1) + (x), (realY1) - (y))) \ + *(ptr1 + (x) - (py)) = color1; \ + if (IS_IN_CLIP((realX2) - (x), (realY2) - (y))) \ + *(ptr2 - (x) - (py)) = color1; \ + if (IS_IN_CLIP((realX2) - (y), (realY2) - (x))) \ + *(ptr2 - (y) - (px)) = color1; \ + if (IS_IN_CLIP((realX3) - (y), (realY3) + (x))) \ + *(ptr3 - (y) + (px)) = color1; \ + if (IS_IN_CLIP((realX3) - (x), (realY3) + (y))) \ + *(ptr3 - (x) + (py)) = color1; \ + if (IS_IN_CLIP((realX4) + (x), (realY4) + (y))) \ + *(ptr4 + (x) + (py)) = color2; \ + if (IS_IN_CLIP((realX4) + (y), (realY4) + (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) @@ -127,6 +175,16 @@ inline frac_t fp_sqroot(uint32 x) { this->blendPixelPtr(ptr + (x) - (py), color, a); \ } while (0) +#define BE_DRAWCIRCLE_BCOLOR_TR_CW_CLIP(ptr,x,y,px,py,a,realX,realY) do { \ + if (IS_IN_CLIP((realX) + (y), (realY) - (x))) \ + this->blendPixelPtr(ptr + (y) - (px), color, a); \ +} while (0) + +#define BE_DRAWCIRCLE_BCOLOR_TR_CCW_CLIP(ptr,x,y,px,py,a,realX,realY) do { \ + if (IS_IN_CLIP((realX) + (x), (realY) - (y))) \ + 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) @@ -135,6 +193,16 @@ inline frac_t fp_sqroot(uint32 x) { this->blendPixelPtr(ptr - (y) - (px), color, a); \ } while (0) +#define BE_DRAWCIRCLE_BCOLOR_TL_CW_CLIP(ptr,x,y,px,py,a,realX,realY) do { \ + if (IS_IN_CLIP((realX) - (x), (realY) - (y))) \ + this->blendPixelPtr(ptr - (x) - (py), color, a); \ +} while (0) + +#define BE_DRAWCIRCLE_BCOLOR_TL_CCW_CLIP(ptr,x,y,px,py,a,realX,realY) do { \ + if (IS_IN_CLIP((realX) - (y), (realY) - (x))) \ + 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) @@ -143,6 +211,16 @@ inline frac_t fp_sqroot(uint32 x) { this->blendPixelPtr(ptr - (x) + (py), color, a); \ } while (0) +#define BE_DRAWCIRCLE_BCOLOR_BL_CW_CLIP(ptr,x,y,px,py,a,realX,realY) do { \ + if (IS_IN_CLIP((realX) - (y), (realY) + (x))) \ + this->blendPixelPtr(ptr - (y) + (px), color, a); \ +} while (0) + +#define BE_DRAWCIRCLE_BCOLOR_BL_CCW_CLIP(ptr,x,y,px,py,a,realX,realY) do { \ + if (IS_IN_CLIP((realX) - (x), (realY) + (y))) \ + 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) @@ -151,6 +229,16 @@ inline frac_t fp_sqroot(uint32 x) { this->blendPixelPtr(ptr + (y) + (px), color, a); \ } while (0) +#define BE_DRAWCIRCLE_BCOLOR_BR_CW_CLIP(ptr,x,y,px,py,a,realX,realY) do { \ + if (IS_IN_CLIP((realX) + (x), (realY) + (y))) \ + this->blendPixelPtr(ptr + (x) + (py), color, a); \ +} while (0) + +#define BE_DRAWCIRCLE_BCOLOR_BR_CCW_CLIP(ptr,x,y,px,py,a,realX,realY) do { \ + if (IS_IN_CLIP((realX) + (y), (realY) + (x))) \ + 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; \ @@ -170,6 +258,42 @@ inline frac_t fp_sqroot(uint32 x) { BE_DRAWCIRCLE_XCOLOR_BOTTOM(ptr3,ptr4,x,y,px,py); \ } while (0) +#define IS_IN_CLIP(x,y) (_clippingArea.left <= (x) && (x) < _clippingArea.right \ + && _clippingArea.top <= (y) && (y) < _clippingArea.bottom) + +#define BE_DRAWCIRCLE_XCOLOR_TOP_CLIP(ptr1,ptr2,x,y,px,py,realX1,realY1,realX2,realY2) do { \ + if (IS_IN_CLIP((realX1) + (y), (realY1) - (x))) \ + *(ptr1 + (y) - (px)) = color1; \ +\ + if (IS_IN_CLIP((realX1) + (x), (realY1) - (y))) \ + *(ptr1 + (x) - (py)) = color2; \ +\ + if (IS_IN_CLIP((realX2) - (x), (realY2) - (y))) \ + *(ptr2 - (x) - (py)) = color2; \ +\ + if (IS_IN_CLIP((realX2) - (y), (realY2) - (x))) \ + *(ptr2 - (y) - (px)) = color1; \ +} while (0) + +#define BE_DRAWCIRCLE_XCOLOR_BOTTOM_CLIP(ptr3,ptr4,x,y,px,py,realX3,realY3,realX4,realY4) do { \ + if (IS_IN_CLIP((realX3) - (y), (realY3) + (x))) \ + *(ptr3 - (y) + (px)) = color3; \ +\ + if (IS_IN_CLIP((realX3) - (x), (realY3) + (y))) \ + *(ptr3 - (x) + (py)) = color4; \ +\ + if (IS_IN_CLIP((realX4) + (x), (realY4) + (y))) \ + *(ptr4 + (x) + (py)) = color4; \ +\ + if (IS_IN_CLIP((realX4) + (y), (realY4) + (x))) \ + *(ptr4 + (y) + (px)) = color3; \ +} while (0) + +#define BE_DRAWCIRCLE_XCOLOR_CLIP(ptr1,ptr2,ptr3,ptr4,x,y,px,py,realX1,realY1,realX2,realY2,realX3,realY3,realX4,realY4) do { \ + BE_DRAWCIRCLE_XCOLOR_TOP_CLIP(ptr1,ptr2,x,y,px,py,realX1,realY1,realX2,realY2); \ + BE_DRAWCIRCLE_XCOLOR_BOTTOM_CLIP(ptr3,ptr4,x,y,px,py,realX3,realY3,realX4,realY4); \ +} while (0) + #define BE_RESET() do { \ f = 1 - r; \ @@ -337,6 +461,45 @@ void colorFill(PixelType *first, PixelType *last, PixelType color) { } } +template<typename PixelType> +void colorFillClip(PixelType *first, PixelType *last, PixelType color, int realX, int realY, Common::Rect &clippingArea) { + if (realY < clippingArea.top || realY >= clippingArea.bottom) + return; + + register int count = (last - first); + + if (realX > clippingArea.right || realX + count < clippingArea.left) + return; + + if (realX < clippingArea.left) { + register int diff = (clippingArea.left - realX); + realX += diff; + count -= diff; + } + + if (clippingArea.right <= realX + count) { + register int diff = (realX + count - clippingArea.right); + count -= diff; + } + + if (!count) + return; + + register int n = (count + 7) >> 3; + switch (count % 8) { + case 0: do { + *first++ = color; + case 7: *first++ = color; + case 6: *first++ = color; + case 5: *first++ = color; + case 4: *first++ = color; + case 3: *first++ = color; + case 2: *first++ = color; + case 1: *first++ = color; + } while (--n > 0); + } +} + VectorRenderer *createRenderer(int mode) { #ifdef DISABLE_FANCY_THEMES @@ -376,6 +539,7 @@ VectorRendererSpec(PixelFormat format) : _alphaMask((0xFF >> format.aLoss) << format.aShift) { _bitmapAlphaColor = _format.RGBToColor(255, 0, 255); + _clippingArea = Common::Rect(0, 0, 32767, 32767); } /**************************** @@ -441,13 +605,56 @@ template<typename PixelType> void VectorRendererSpec<PixelType>:: gradientFill(PixelType *ptr, int width, int x, int y) { bool ox = ((y & 1) == 1); - int stripSize; int curGrad = 0; while (_gradIndexes[curGrad + 1] <= y) curGrad++; - stripSize = _gradIndexes[curGrad + 1] - _gradIndexes[curGrad]; + // precalcGradient assures that _gradIndexes entries always differ in + // their value. This assures stripSize is always different from zero. + int stripSize = _gradIndexes[curGrad + 1] - _gradIndexes[curGrad]; + + int grad = (((y - _gradIndexes[curGrad]) % stripSize) << 2) / stripSize; + + // Dithering: + // +--+ +--+ +--+ +--+ + // | | | | | *| | *| + // | | | *| |* | |**| + // +--+ +--+ +--+ +--+ + // 0 1 2 3 + if (grad == 0 || + _gradCache[curGrad] == _gradCache[curGrad + 1] || // no color change + stripSize < 2) { // the stip is small + colorFill<PixelType>(ptr, ptr + width, _gradCache[curGrad]); + } else if (grad == 3 && ox) { + colorFill<PixelType>(ptr, ptr + width, _gradCache[curGrad + 1]); + } else { + for (int j = x; j < x + width; j++, ptr++) { + bool oy = ((j & 1) == 1); + + if ((ox && oy) || + ((grad == 2 || grad == 3) && ox && !oy) || + (grad == 3 && oy)) + *ptr = _gradCache[curGrad + 1]; + else + *ptr = _gradCache[curGrad]; + } + } +} + +template<typename PixelType> +void VectorRendererSpec<PixelType>:: +gradientFillClip(PixelType *ptr, int width, int x, int y, int realX, int realY) { + if (realY < _clippingArea.top || realY >= _clippingArea.bottom) return; + bool ox = ((y & 1) == 1); + int curGrad = 0; + + while (_gradIndexes[curGrad + 1] <= y) + curGrad++; + + // precalcGradient assures that _gradIndexes entries always differ in + // their value. This assures stripSize is always different from zero. + int stripSize = _gradIndexes[curGrad + 1] - _gradIndexes[curGrad]; int grad = (((y - _gradIndexes[curGrad]) % stripSize) << 2) / stripSize; @@ -465,6 +672,7 @@ gradientFill(PixelType *ptr, int width, int x, int y) { colorFill<PixelType>(ptr, ptr + width, _gradCache[curGrad + 1]); } else { for (int j = x; j < x + width; j++, ptr++) { + if (realX + j - x < _clippingArea.left || realX + j - x >= _clippingArea.right) continue; bool oy = ((j & 1) == 1); if ((ox && oy) || @@ -502,6 +710,44 @@ fillSurface() { template<typename PixelType> void VectorRendererSpec<PixelType>:: +fillSurfaceClip(Common::Rect clipping) { + int w = _activeSurface->w; + int h = _activeSurface->h; + if (clipping.isEmpty() || (clipping.left == 0 && clipping.top == 0 && clipping.right == w && clipping.bottom == h)) { + fillSurface(); + return; + } + + byte *ptr = (byte *)_activeSurface->getPixels(); + int pitch = _activeSurface->pitch; + + if (Base::_fillMode == kFillBackground || Base::_fillMode == kFillForeground) { + PixelType color = (Base::_fillMode == kFillBackground ? _bgColor : _fgColor); + byte *ptrLeft = (ptr + _clippingArea.left), *ptrRight = ptr + _clippingArea.right; + for (int i = 0; i < h; i++) { + if (_clippingArea.top <= i && i < _clippingArea.bottom) { + colorFill<PixelType>((PixelType *)ptrLeft, (PixelType *)ptrRight, color); + } + + ptrLeft += pitch; + ptrRight += pitch; + } + + } else if (Base::_fillMode == kFillGradient) { + precalcGradient(h); + + for (int i = 0; i < h; i++) { + if (_clippingArea.top <= i && i < _clippingArea.bottom) { + gradientFill((PixelType *)ptr + _clippingArea.left, _clippingArea.width(), 0, i); + } + + ptr += pitch; + } + } +} + +template<typename PixelType> +void VectorRendererSpec<PixelType>:: copyFrame(OSystem *sys, const Common::Rect &r) { sys->copyRectToOverlay( @@ -553,7 +799,59 @@ blitSubSurface(const Graphics::Surface *source, const Common::Rect &r) { template<typename PixelType> void VectorRendererSpec<PixelType>:: -blitAlphaBitmap(const Graphics::Surface *source, const Common::Rect &r) { +blitSubSurfaceClip(const Graphics::Surface *source, const Common::Rect &r, const Common::Rect &clipping) { + if (clipping.isEmpty() || clipping.contains(r)) { + blitSubSurface(source, r); + return; + } + + int16 x = r.left; + int16 y = r.top; + + if (r.width() > source->w) + x = x + (r.width() >> 1) - (source->w >> 1); + + if (r.height() > source->h) + y = y + (r.height() >> 1) - (source->h >> 1); + + int w = source->w, h = source->h; + int usedW = w, usedH = h; + int offsetX = 0, offsetY = 0; + + if (x > clipping.right || x + w < clipping.left) return; + if (y > clipping.bottom || y + h < clipping.top) return; + if (x < clipping.left) { + offsetX = clipping.left - x; + usedW -= offsetX; + x = clipping.left; + } + if (y < clipping.top) { + offsetY = clipping.top - y; + usedH -= offsetY; + y = clipping.top; + } + if (usedW > clipping.width()) usedW = clipping.width(); + if (usedH > clipping.height()) usedH = clipping.height(); + + byte *dst_ptr = (byte *)_activeSurface->getBasePtr(x, y); + const byte *src_ptr = (const byte *)source->getBasePtr(offsetX, offsetY); + + const int dst_pitch = _activeSurface->pitch; + const int src_pitch = source->pitch; + + int lines = usedH; + const int sz = usedW * sizeof(PixelType); + + while (lines--) { + memcpy(dst_ptr, src_ptr, sz); + dst_ptr += dst_pitch; + src_ptr += src_pitch; + } +} + +template<typename PixelType> +void VectorRendererSpec<PixelType>:: +blitKeyBitmap(const Graphics::Surface *source, const Common::Rect &r) { int16 x = r.left; int16 y = r.top; @@ -589,6 +887,99 @@ blitAlphaBitmap(const Graphics::Surface *source, const Common::Rect &r) { template<typename PixelType> void VectorRendererSpec<PixelType>:: +blitAlphaBitmap(Graphics::TransparentSurface *source, const Common::Rect &r, GUI::ThemeEngine::AutoScaleMode autoscale, + Graphics::DrawStep::VectorAlignment xAlign, Graphics::DrawStep::VectorAlignment yAlign, int alpha) { + if (autoscale == GUI::ThemeEngine::kAutoScaleStretch) { + source->blit(*_activeSurface, r.left, r.top, Graphics::FLIP_NONE, + nullptr, TS_ARGB(alpha, 255, 255, 255), + r.width(), r.height()); + } else if (autoscale == GUI::ThemeEngine::kAutoScaleFit) { + double ratio = (double)r.width() / source->w; + double ratio2 = (double)r.height() / source->h; + + if (ratio2 < ratio) + ratio = ratio2; + + int offx = 0, offy = 0; + if (xAlign == Graphics::DrawStep::kVectorAlignCenter) + offx = (r.width() - (int)(source->w * ratio)) >> 1; + + if (yAlign == Graphics::DrawStep::kVectorAlignCenter) + offy = (r.height() - (int)(source->h * ratio)) >> 1; + + source->blit(*_activeSurface, r.left + offx, r.top + offy, Graphics::FLIP_NONE, + nullptr, TS_ARGB(alpha, 255, 255, 255), + (int)(source->w * ratio), (int)(source->h * ratio)); + + } else if (autoscale == GUI::ThemeEngine::kAutoScaleNinePatch) { + Graphics::NinePatchBitmap nine(source, false); + nine.blit(*_activeSurface, r.left, r.top, r.width(), r.height()); + } else { + source->blit(*_activeSurface, r.left, r.top); + } +} + +template<typename PixelType> +void VectorRendererSpec<PixelType>:: +blitKeyBitmapClip(const Graphics::Surface *source, const Common::Rect &r, const Common::Rect &clipping) { + if (clipping.isEmpty() || clipping.contains(r)) { + blitKeyBitmap(source, r); + return; + } + + int16 x = r.left; + int16 y = r.top; + + if (r.width() > source->w) + x = x + (r.width() >> 1) - (source->w >> 1); + + if (r.height() > source->h) + y = y + (r.height() >> 1) - (source->h >> 1); + + int w = source->w, h = source->h; + int usedW = w, usedH = h; + int offsetX = 0, offsetY = 0; + + if (x > clipping.right || x + w < clipping.left) return; + if (y > clipping.bottom || y + h < clipping.top) return; + if (x < clipping.left) { + offsetX = clipping.left - x; + usedW -= offsetX; + x = clipping.left; + } + if (y < clipping.top) { + offsetY = clipping.top - y; + usedH -= offsetY; + y = clipping.top; + } + if (usedW > clipping.width()) usedW = clipping.width(); + if (usedH > clipping.height()) usedH = clipping.height(); + + PixelType *dst_ptr = (PixelType *)_activeSurface->getBasePtr(x, y); + const PixelType *src_ptr = (const PixelType *)source->getBasePtr(offsetX, offsetY); + + int dst_pitch = _activeSurface->pitch / _activeSurface->format.bytesPerPixel; + int src_pitch = source->pitch / source->format.bytesPerPixel; + + h = usedH; + while (h--) { + w = usedW; + + while (w--) { + if (*src_ptr != _bitmapAlphaColor) + *dst_ptr = *src_ptr; + + dst_ptr++; + src_ptr++; + } + + dst_ptr = dst_ptr - usedW + dst_pitch; + src_ptr = src_ptr - usedW + src_pitch; + } +} + +template<typename PixelType> +void VectorRendererSpec<PixelType>:: applyScreenShading(GUI::ThemeEngine::ShadingStyle shadingStyle) { int pixels = _activeSurface->w * _activeSurface->h; PixelType *ptr = (PixelType *)_activeSurface->getPixels(); @@ -666,6 +1057,13 @@ blendPixelPtr(PixelType *ptr, PixelType color, uint8 alpha) { template<typename PixelType> inline void VectorRendererSpec<PixelType>:: +blendPixelPtrClip(PixelType *ptr, PixelType color, uint8 alpha, int x, int y) { + if (IS_IN_CLIP(x, y)) + blendPixelPtr(ptr, color, alpha); +} + +template<typename PixelType> +inline void VectorRendererSpec<PixelType>:: blendPixelDestAlphaPtr(PixelType *ptr, PixelType color, uint8 alpha) { int idst = *ptr; // This function is only used for corner pixels in rounded rectangles, so @@ -709,6 +1107,36 @@ darkenFill(PixelType *ptr, PixelType *end) { } } +template<typename PixelType> +inline void VectorRendererSpec<PixelType>:: +darkenFillClip(PixelType *ptr, PixelType *end, int x, int y) { + PixelType mask = (PixelType)((3 << _format.rShift) | (3 << _format.gShift) | (3 << _format.bShift)); + + if (!g_system->hasFeature(OSystem::kFeatureOverlaySupportsAlpha)) { + // !kFeatureOverlaySupportsAlpha (but might have alpha bits) + + while (ptr != end) { + if (IS_IN_CLIP(x, y)) *ptr = ((*ptr & ~mask) >> 2) | _alphaMask; + ++ptr; + ++x; + } + } else { + // kFeatureOverlaySupportsAlpha + // assuming at least 3 alpha bits + + mask |= 3 << _format.aShift; + PixelType addA = (PixelType)(3 << (_format.aShift + 6 - _format.aLoss)); + + while (ptr != end) { + // Darken the color, and increase the alpha + // (0% -> 75%, 100% -> 100%) + if (IS_IN_CLIP(x, y)) *ptr = (PixelType)(((*ptr & ~mask) >> 2) + addA); + ++ptr; + ++x; + } + } +} + /******************************************************************** ******************************************************************** * Primitive shapes drawing - Public API calls - VectorRendererSpec * @@ -773,8 +1201,8 @@ drawLine(int x1, int y1, int x2, int y2) { SWAP(y1, y2); } - int dx = ABS(x2 - x1); - int dy = ABS(y2 - y1); + uint dx = ABS(x2 - x1); + uint dy = ABS(y2 - y1); // this is a point, not a line. stoopid. if (dy == 0 && dx == 0) @@ -803,7 +1231,7 @@ drawLine(int x1, int y1, int x2, int y2) { ptr += pitch; } - } else if (ABS(dx) == ABS(dy)) { // diagonal lines + } else if (dx == dy) { // diagonal lines // these ones also use a fixed pitch increase pitch += (x2 > x1) ? 1 : -1; @@ -817,6 +1245,77 @@ drawLine(int x1, int y1, int x2, int y2) { } } +template<typename PixelType> +void VectorRendererSpec<PixelType>:: +drawLineClip(int x1, int y1, int x2, int y2, Common::Rect clipping) { + x1 = CLIP(x1, 0, (int)Base::_activeSurface->w); + x2 = CLIP(x2, 0, (int)Base::_activeSurface->w); + y1 = CLIP(y1, 0, (int)Base::_activeSurface->h); + y2 = CLIP(y2, 0, (int)Base::_activeSurface->h); + + // we draw from top to bottom + if (y2 < y1) { + SWAP(x1, x2); + SWAP(y1, y2); + } + + uint dx = ABS(x2 - x1); + uint dy = ABS(y2 - y1); + + // this is a point, not a line. stoopid. + if (dy == 0 && dx == 0) + return; + + if (Base::_strokeWidth == 0) + return; + + PixelType *ptr = (PixelType *)_activeSurface->getBasePtr(x1, y1); + int pitch = _activeSurface->pitch / _activeSurface->format.bytesPerPixel; + int st = Base::_strokeWidth >> 1; + + Common::Rect backup = _clippingArea; + _clippingArea = clipping; + bool needsClipping = !_clippingArea.isEmpty() && (!_clippingArea.contains(x1, y1) || !_clippingArea.contains(x2, y2)); + if (!needsClipping) { + drawLine(x1, y1, x2, y2); + _clippingArea = backup; + return; + } + + int ptr_x = x1, ptr_y = y1; + + if (dy == 0) { // horizontal lines + colorFillClip<PixelType>(ptr, ptr + dx + 1, (PixelType)_fgColor, x1, y1, _clippingArea); + + for (int i = 0, p = pitch; i < st; ++i, p += pitch) { + colorFillClip<PixelType>(ptr + p, ptr + dx + 1 + p, (PixelType)_fgColor, x1, y1 + p/pitch, _clippingArea); + colorFillClip<PixelType>(ptr - p, ptr + dx + 1 - p, (PixelType)_fgColor, x1, y1 - p/pitch, _clippingArea); + } + + } else if (dx == 0) { // vertical lines + // these ones use a static pitch increase. + while (y1++ <= y2) { + colorFillClip<PixelType>(ptr - st, ptr + st, (PixelType)_fgColor, x1 - st, ptr_y, _clippingArea); + ptr += pitch; + ++ptr_y; + } + + } else if (dx == dy) { // diagonal lines + // these ones also use a fixed pitch increase + pitch += (x2 > x1) ? 1 : -1; + + while (dy--) { + colorFillClip<PixelType>(ptr - st, ptr + st, (PixelType)_fgColor, ptr_x - st, ptr_y, _clippingArea); + ptr += pitch; + ++ptr_y; + if (x2 > x1) ++ptr_x; else --ptr_x; + } + + } else { // generic lines, use the standard algorithm... + drawLineAlgClip(x1, y1, x2, y2, dx, dy, (PixelType)_fgColor); + } +} + /** CIRCLES **/ template<typename PixelType> void VectorRendererSpec<PixelType>:: @@ -856,6 +1355,70 @@ drawCircle(int x, int y, int r) { } } +template<typename PixelType> +void VectorRendererSpec<PixelType>:: +drawCircleClip(int x, int y, int r, Common::Rect clipping) { + if (x + r > Base::_activeSurface->w || y + r > Base::_activeSurface->h || + x - r < 0 || y - r < 0 || x == 0 || y == 0 || r <= 0) + return; + + Common::Rect backup = _clippingArea; + _clippingArea = clipping; + bool useClippingVersions = !(_clippingArea.isEmpty() || _clippingArea.contains(Common::Rect(x - r, y - r, x + r, y + r))); + + if (Base::_fillMode != kFillDisabled && Base::_shadowOffset + && x + r + Base::_shadowOffset < Base::_activeSurface->w + && y + r + Base::_shadowOffset < Base::_activeSurface->h) { + if (useClippingVersions) + drawCircleAlgClip(x + Base::_shadowOffset + 1, y + Base::_shadowOffset + 1, r, 0, kFillForeground); + else + drawCircleAlg(x + Base::_shadowOffset + 1, y + Base::_shadowOffset + 1, r, 0, kFillForeground); + } + + switch (Base::_fillMode) { + case kFillDisabled: + if (Base::_strokeWidth) { + if (useClippingVersions) + drawCircleAlgClip(x, y, r, _fgColor, kFillDisabled); + else + drawCircleAlg(x, y, r, _fgColor, kFillDisabled); + } + break; + + case kFillForeground: + if (useClippingVersions) + drawCircleAlgClip(x, y, r, _fgColor, kFillForeground); + else + drawCircleAlg(x, y, r, _fgColor, kFillForeground); + break; + + case kFillBackground: + if (Base::_strokeWidth > 1) { + if (useClippingVersions) { + drawCircleAlgClip(x, y, r, _fgColor, kFillForeground); + drawCircleAlgClip(x, y, r - Base::_strokeWidth, _bgColor, kFillBackground); + } else { + drawCircleAlg(x, y, r, _fgColor, kFillForeground); + drawCircleAlg(x, y, r - Base::_strokeWidth, _bgColor, kFillBackground); + } + } else { + if (useClippingVersions) { + drawCircleAlgClip(x, y, r, _bgColor, kFillBackground); + drawCircleAlgClip(x, y, r, _fgColor, kFillDisabled); + } else { + drawCircleAlg(x, y, r, _bgColor, kFillBackground); + drawCircleAlg(x, y, r, _fgColor, kFillDisabled); + } + } + break; + + case kFillGradient: + break; + } + + _clippingArea = backup; +} + /** SQUARES **/ template<typename PixelType> void VectorRendererSpec<PixelType>:: @@ -893,6 +1456,67 @@ drawSquare(int x, int y, int w, int h) { } } +template<typename PixelType> +void VectorRendererSpec<PixelType>:: +drawSquareClip(int x, int y, int w, int h, Common::Rect clipping) { + if (x + w > Base::_activeSurface->w || y + h > Base::_activeSurface->h || + w <= 0 || h <= 0 || x < 0 || y < 0) + return; + + Common::Rect backup = _clippingArea; + _clippingArea = clipping; + bool useClippingVersions = !(_clippingArea.isEmpty() || _clippingArea.contains(Common::Rect(x, y, x + w, y + h))); + + if (Base::_fillMode != kFillDisabled && Base::_shadowOffset + && x + w + Base::_shadowOffset < Base::_activeSurface->w + && y + h + Base::_shadowOffset < Base::_activeSurface->h) { + if (useClippingVersions) + drawSquareShadowClip(x, y, w, h, Base::_shadowOffset); + else + drawSquareShadow(x, y, w, h, Base::_shadowOffset); + } + + switch (Base::_fillMode) { + case kFillDisabled: + if (Base::_strokeWidth) { + if (useClippingVersions) + drawSquareAlgClip(x, y, w, h, _fgColor, kFillDisabled); + else + drawSquareAlg(x, y, w, h, _fgColor, kFillDisabled); + } + break; + + case kFillForeground: + if (useClippingVersions) + drawSquareAlgClip(x, y, w, h, _fgColor, kFillForeground); + else + drawSquareAlg(x, y, w, h, _fgColor, kFillForeground); + break; + + case kFillBackground: + if (useClippingVersions) { + drawSquareAlgClip(x, y, w, h, _bgColor, kFillBackground); + drawSquareAlgClip(x, y, w, h, _fgColor, kFillDisabled); + } else { + drawSquareAlg(x, y, w, h, _bgColor, kFillBackground); + drawSquareAlg(x, y, w, h, _fgColor, kFillDisabled); + } + break; + + case kFillGradient: + VectorRendererSpec::drawSquareAlg(x, y, w, h, 0, kFillGradient); + if (Base::_strokeWidth) { + if (useClippingVersions) + drawSquareAlgClip(x, y, w, h, _fgColor, kFillDisabled); + else + drawSquareAlg(x, y, w, h, _fgColor, kFillDisabled); + } + break; + } + + _clippingArea = backup; +} + /** ROUNDED SQUARES **/ template<typename PixelType> void VectorRendererSpec<PixelType>:: @@ -919,6 +1543,43 @@ drawRoundedSquare(int x, int y, int r, int w, int h) { template<typename PixelType> void VectorRendererSpec<PixelType>:: +drawRoundedSquareClip(int x, int y, int r, int w, int h, Common::Rect clipping) { + if (x + w > Base::_activeSurface->w || y + h > Base::_activeSurface->h || + w <= 0 || h <= 0 || x < 0 || y < 0 || r <= 0) + return; + + if ((r * 2) > w || (r * 2) > h) + r = MIN(w / 2, h / 2); + + if (r <= 0) + return; + + Common::Rect backup = _clippingArea; + _clippingArea = clipping; + bool useOriginal = (_clippingArea.isEmpty() || _clippingArea.contains(Common::Rect(x, y, x + w, y + h))); + + if (Base::_fillMode != kFillDisabled && Base::_shadowOffset + && x + w + Base::_shadowOffset + 1 < Base::_activeSurface->w + && y + h + Base::_shadowOffset + 1 < Base::_activeSurface->h + && h > (Base::_shadowOffset + 1) * 2) { + if (useOriginal) { + drawRoundedSquareShadow(x, y, r, w, h, Base::_shadowOffset); + } else { + drawRoundedSquareShadowClip(x, y, r, w, h, Base::_shadowOffset); + } + } + + if (useOriginal) { + drawRoundedSquareAlg(x, y, r, w, h, _fgColor, Base::_fillMode); + } else { + drawRoundedSquareAlgClip(x, y, r, w, h, _fgColor, Base::_fillMode); + } + + _clippingArea = backup; +} + +template<typename PixelType> +void VectorRendererSpec<PixelType>:: drawTab(int x, int y, int r, int w, int h) { if (x + w > Base::_activeSurface->w || y + h > Base::_activeSurface->h || w <= 0 || h <= 0 || x < 0 || y < 0 || r > w || r > h) @@ -955,6 +1616,66 @@ drawTab(int x, int y, int r, int w, int h) { template<typename PixelType> void VectorRendererSpec<PixelType>:: +drawTabClip(int x, int y, int r, int w, int h, Common::Rect clipping) { + if (x + w > Base::_activeSurface->w || y + h > Base::_activeSurface->h || + w <= 0 || h <= 0 || x < 0 || y < 0 || r > w || r > h) + return; + + Common::Rect backup = _clippingArea; + _clippingArea = clipping; + bool useClippingVersions = !(_clippingArea.isEmpty() || _clippingArea.contains(Common::Rect(x, y, x + w, y + h))); + + if (r == 0 && Base::_bevel > 0) { + if (useClippingVersions) + drawBevelTabAlgClip(x, y, w, h, Base::_bevel, _bevelColor, _fgColor, (Base::_dynamicData >> 16), (Base::_dynamicData & 0xFFFF)); + else + drawBevelTabAlg(x, y, w, h, Base::_bevel, _bevelColor, _fgColor, (Base::_dynamicData >> 16), (Base::_dynamicData & 0xFFFF)); + _clippingArea = backup; + return; + } + + if (r == 0) { + _clippingArea = backup; + return; + } + + switch (Base::_fillMode) { + case kFillDisabled: + // FIXME: Implement this + _clippingArea = backup; + return; + + case kFillGradient: + case kFillBackground: + // 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.) + if (useClippingVersions) { + drawTabShadowClip(x, y, w - 2, h, r); + drawTabAlgClip(x, y, w - 2, h, r, _bgColor, Base::_fillMode); + if (Base::_strokeWidth) + drawTabAlgClip(x, y, w, h, r, _fgColor, kFillDisabled, (Base::_dynamicData >> 16), (Base::_dynamicData & 0xFFFF)); + } else { + 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: + if (useClippingVersions) + drawTabAlgClip(x, y, w, h, r, _fgColor, Base::_fillMode); + else + drawTabAlg(x, y, w, h, r, _fgColor, Base::_fillMode); + break; + } + + _clippingArea = backup; +} + +template<typename PixelType> +void VectorRendererSpec<PixelType>:: drawTriangle(int x, int y, int w, int h, TriangleOrientation orient) { if (x + w > Base::_activeSurface->w || y + h > Base::_activeSurface->h) @@ -1021,8 +1742,88 @@ drawTriangle(int x, int y, int w, int h, TriangleOrientation orient) { } } +template<typename PixelType> +void VectorRendererSpec<PixelType>:: +drawTriangleClip(int x, int y, int w, int h, TriangleOrientation orient, Common::Rect clipping) { + if (x + w > Base::_activeSurface->w || y + h > Base::_activeSurface->h) + return; + PixelType color = 0; + if (Base::_strokeWidth <= 1) { + if (Base::_fillMode == kFillForeground) + color = _fgColor; + else if (Base::_fillMode == kFillBackground) + color = _bgColor; + } else { + if (Base::_fillMode == kFillDisabled) + return; + color = _fgColor; + } + + if (Base::_dynamicData != 0) + orient = (TriangleOrientation)Base::_dynamicData; + + Common::Rect backup = _clippingArea; + _clippingArea = clipping; + bool useClippingVersions = !(_clippingArea.isEmpty() || _clippingArea.contains(Common::Rect(x, y, x + w, y + h))); + + if (w == h) { + int newW = w; + + switch (orient) { + case kTriangleUp: + case kTriangleDown: + if (useClippingVersions) + drawTriangleVertAlgClip(x, y, newW, newW, (orient == kTriangleDown), color, Base::_fillMode); + else + drawTriangleVertAlg(x, y, newW, newW, (orient == kTriangleDown), color, Base::_fillMode); + break; + + case kTriangleLeft: + case kTriangleRight: + case kTriangleAuto: + break; + } + + if (Base::_strokeWidth > 0) + if (Base::_fillMode == kFillBackground || Base::_fillMode == kFillGradient) { + if (useClippingVersions) + drawTriangleVertAlgClip(x, y, newW, newW, (orient == kTriangleDown), color, Base::_fillMode); + else + drawTriangleVertAlg(x, y, newW, newW, (orient == kTriangleDown), color, Base::_fillMode); + } + } else { + int newW = w; + int newH = h; + + switch (orient) { + case kTriangleUp: + case kTriangleDown: + if (useClippingVersions) + drawTriangleVertAlgClip(x, y, newW, newH, (orient == kTriangleDown), color, Base::_fillMode); + else + drawTriangleVertAlg(x, y, newW, newH, (orient == kTriangleDown), color, Base::_fillMode); + break; + + case kTriangleLeft: + case kTriangleRight: + case kTriangleAuto: + break; + } + + if (Base::_strokeWidth > 0) { + if (Base::_fillMode == kFillBackground || Base::_fillMode == kFillGradient) { + if (useClippingVersions) + drawTriangleVertAlgClip(x, y, newW, newH, (orient == kTriangleDown), _fgColor, kFillDisabled); + else + drawTriangleVertAlg(x, y, newW, newH, (orient == kTriangleDown), _fgColor, kFillDisabled); + } + } + } + + _clippingArea = backup; +} /******************************************************************** @@ -1034,6 +1835,11 @@ drawTriangle(int x, int y, int w, int h, TriangleOrientation orient) { template<typename PixelType> void VectorRendererSpec<PixelType>:: drawTabAlg(int x1, int y1, int w, int h, int r, PixelType color, VectorRenderer::FillMode fill_m, int baseLeft, int baseRight) { + // Don't draw anything for empty rects. + if (w <= 0 || h <= 0) { + return; + } + int f, ddF_x, ddF_y; int x, y, px, py; int pitch = _activeSurface->pitch / _activeSurface->format.bytesPerPixel; @@ -1128,6 +1934,120 @@ drawTabAlg(int x1, int y1, int w, int h, int r, PixelType color, VectorRenderer: } } +template<typename PixelType> +void VectorRendererSpec<PixelType>:: +drawTabAlgClip(int x1, int y1, int w, int h, int r, PixelType color, VectorRenderer::FillMode fill_m, int baseLeft, int baseRight) { + // Don't draw anything for empty rects. + if (w <= 0 || h <= 0) { + return; + } + + int f, ddF_x, ddF_y; + int x, y, px, py; + int pitch = _activeSurface->pitch / _activeSurface->format.bytesPerPixel; + int sw = 0, sp = 0, hp = 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_fill = (PixelType *)Base::_activeSurface->getBasePtr(x1, y1); + int tl_x = x1 + r, tl_y = y1 + r; + int tr_x = x1 + w - r, tr_y = y1 + r; + int fill_x = x1, fill_y = y1; + + int real_radius = r; + int short_h = h - r + 2; + int long_h = h; + + if (fill_m == kFillDisabled) { + while (sw++ < Base::_strokeWidth) { + colorFillClip<PixelType>(ptr_fill + sp + r, ptr_fill + w + 1 + sp - r, color, fill_x + r, fill_y + sp/pitch, _clippingArea); + colorFillClip<PixelType>(ptr_fill + hp - sp + r, ptr_fill + w + hp + 1 - sp - r, color, fill_x + r, fill_y + hp / pitch - sp / pitch, _clippingArea); + sp += pitch; + + BE_RESET(); + r--; + + while (x++ < y) { + BE_ALGORITHM(); + BE_DRAWCIRCLE_TOP_CLIP(ptr_tr, ptr_tl, x, y, px, py, tr_x, tr_y, tl_x, tl_y); + + if (Base::_strokeWidth > 1) + BE_DRAWCIRCLE_TOP_CLIP(ptr_tr, ptr_tl, x, y, px - pitch, py, tr_x, tr_y, tl_x, tl_y); + } + } + + ptr_fill += pitch * real_radius; + fill_y += real_radius; + while (short_h--) { + colorFillClip<PixelType>(ptr_fill, ptr_fill + Base::_strokeWidth, color, fill_x, fill_y, _clippingArea); + colorFillClip<PixelType>(ptr_fill + w - Base::_strokeWidth + 1, ptr_fill + w + 1, color, fill_x + w - Base::_strokeWidth + 1, fill_y, _clippingArea); + ptr_fill += pitch; + ++fill_y; + } + + if (baseLeft) { + sw = 0; + ptr_fill = (PixelType *)Base::_activeSurface->getBasePtr(x1, y1 + h + 1); + fill_x = x1; + fill_y = y1 + h + 1; + while (sw++ < Base::_strokeWidth) { + colorFillClip<PixelType>(ptr_fill - baseLeft, ptr_fill, color, fill_x - baseLeft, fill_y, _clippingArea); + ptr_fill += pitch; + ++fill_y; + } + } + + if (baseRight) { + sw = 0; + ptr_fill = (PixelType *)Base::_activeSurface->getBasePtr(x1 + w, y1 + h + 1); + fill_x = x1 + w; + fill_y = y1 + h + 1; + while (sw++ < Base::_strokeWidth) { + colorFillClip<PixelType>(ptr_fill, ptr_fill + baseRight, color, fill_x, fill_y, _clippingArea); + ptr_fill += pitch; + ++fill_y; + } + } + } else { + BE_RESET(); + + precalcGradient(long_h); + + PixelType color1, color2; + color1 = color2 = color; + + while (x++ < y) { + BE_ALGORITHM(); + + if (fill_m == kFillGradient) { + color1 = calcGradient(real_radius - x, long_h); + color2 = calcGradient(real_radius - y, long_h); + + gradientFillClip(ptr_tl - x - py, w - 2 * r + 2 * x, x1 + r - x - y, real_radius - y, tl_x - x, tl_y - y); + gradientFillClip(ptr_tl - y - px, w - 2 * r + 2 * y, x1 + r - y - x, real_radius - x, tl_x - y, tl_y - x); + + BE_DRAWCIRCLE_XCOLOR_TOP_CLIP(ptr_tr, ptr_tl, x, y, px, py, tr_x, tr_y, tl_x, tl_y); + } else { + colorFillClip<PixelType>(ptr_tl - x - py, ptr_tr + x - py, color, tl_x - x, tl_y - y, _clippingArea); + colorFillClip<PixelType>(ptr_tl - y - px, ptr_tr + y - px, color, tl_x - y, tl_y - x, _clippingArea); + + BE_DRAWCIRCLE_TOP_CLIP(ptr_tr, ptr_tl, x, y, px, py, tr_x, tr_y, tl_x, tl_y); + } + } + + ptr_fill += pitch * r; + fill_y += r; + while (short_h--) { + if (fill_m == kFillGradient) { + gradientFillClip(ptr_fill, w + 1, x1, real_radius++, fill_x, fill_y); + } else { + colorFillClip<PixelType>(ptr_fill, ptr_fill + w + 1, color, fill_x, fill_y, _clippingArea); + } + ptr_fill += pitch; + ++fill_y; + } + } +} template<typename PixelType> void VectorRendererSpec<PixelType>:: @@ -1190,6 +2110,72 @@ drawTabShadow(int x1, int y1, int w, int h, int r) { } } +template<typename PixelType> +void VectorRendererSpec<PixelType>:: +drawTabShadowClip(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 tl_x = xstart + r, tl_y = ystart + r; + int fill_x = xstart, fill_y = 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) { + blendFillClip(ptr_tl - y - px, ptr_tr + y - px, color, (uint8)alpha, tl_x - y, tl_y - x); + hb |= (1 << x); + } + + if (((1 << y) & hb) == 0) { + blendFillClip(ptr_tl - x - py, ptr_tr + x - py, color, (uint8)alpha, tl_x - x, tl_y - y); + hb |= (1 << y); + } + } + + ptr_fill += pitch * r; + fill_y += r; + while (short_h--) { + blendFillClip(ptr_fill, ptr_fill + width + 1, color, (uint8)alpha, fill_x, fill_y); + ptr_fill += pitch; + ++fill_y; + } + + // 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>:: @@ -1234,10 +2220,66 @@ drawBevelTabAlg(int x, int y, int w, int h, int bevel, PixelType top_color, Pixe } } +template<typename PixelType> +void VectorRendererSpec<PixelType>:: +drawBevelTabAlgClip(int x, int y, int w, int h, int bevel, PixelType top_color, PixelType bottom_color, int baseLeft, int baseRight) { + int pitch = _activeSurface->pitch / _activeSurface->format.bytesPerPixel; + int i, j; + + PixelType *ptr_left = (PixelType *)_activeSurface->getBasePtr(x, y); + int ptr_x = x, ptr_y = y; + + i = bevel; + while (i--) { + colorFillClip<PixelType>(ptr_left, ptr_left + w, top_color, ptr_x, ptr_y, _clippingArea); + ptr_left += pitch; + ++ptr_y; + } + + if (baseLeft > 0) { + i = h - bevel; + ptr_left = (PixelType *)_activeSurface->getBasePtr(x, y); + ptr_x = x; ptr_y = y; + while (i--) { + colorFillClip<PixelType>(ptr_left, ptr_left + bevel, top_color, ptr_x, ptr_y, _clippingArea); + ptr_left += pitch; + ++ptr_y; + } + } + + i = h - bevel; + j = bevel - 1; + ptr_left = (PixelType *)_activeSurface->getBasePtr(x + w - bevel, y); + ptr_x = x + w - bevel; ptr_y = y; + while (i--) { + colorFillClip<PixelType>(ptr_left + j, ptr_left + bevel, bottom_color, ptr_x + j, ptr_y, _clippingArea); + if (j > 0) j--; + ptr_left += pitch; + ++ptr_y; + } + + i = bevel; + ptr_left = (PixelType *)_activeSurface->getBasePtr(x + w - bevel, y + h - bevel); + ptr_x = x + w - bevel; ptr_y = y + h - bevel; + while (i--) { + colorFillClip<PixelType>(ptr_left, ptr_left + baseRight + bevel, bottom_color, ptr_x, ptr_y, _clippingArea); + + if (baseLeft) + colorFillClip<PixelType>(ptr_left - w - baseLeft + bevel, ptr_left - w + bevel + bevel, top_color, ptr_x - w - baseLeft + bevel, ptr_y, _clippingArea); + ptr_left += pitch; + ++ptr_y; + } +} + /** SQUARE ALGORITHM **/ template<typename PixelType> void VectorRendererSpec<PixelType>:: drawSquareAlg(int x, int y, int w, int h, PixelType color, VectorRenderer::FillMode fill_m) { + // Do not draw anything for empty rects. + if (w <= 0 || h <= 0) { + return; + } + PixelType *ptr = (PixelType *)_activeSurface->getBasePtr(x, y); int pitch = _activeSurface->pitch / _activeSurface->format.bytesPerPixel; int max_h = h; @@ -1267,6 +2309,46 @@ drawSquareAlg(int x, int y, int w, int h, PixelType color, VectorRenderer::FillM } } +template<typename PixelType> +void VectorRendererSpec<PixelType>:: +drawSquareAlgClip(int x, int y, int w, int h, PixelType color, VectorRenderer::FillMode fill_m) { + // Do not draw anything for empty rects. + if (w <= 0 || h <= 0) { + return; + } + + PixelType *ptr = (PixelType *)_activeSurface->getBasePtr(x, y); + int pitch = _activeSurface->pitch / _activeSurface->format.bytesPerPixel; + int max_h = h; + int ptr_y = y; + + if (fill_m != kFillDisabled) { + while (h--) { + if (fill_m == kFillGradient) + color = calcGradient(max_h - h, max_h); + + colorFillClip<PixelType>(ptr, ptr + w, color, x, ptr_y, _clippingArea); + ptr += pitch; + ++ptr_y; + } + } else { + int sw = Base::_strokeWidth, sp = 0, hp = pitch * (h - 1); + + while (sw--) { + colorFillClip<PixelType>(ptr + sp, ptr + w + sp, color, x, ptr_y + sp/pitch, _clippingArea); + colorFillClip<PixelType>(ptr + hp - sp, ptr + w + hp - sp, color, x, ptr_y + h - sp/pitch, _clippingArea); + sp += pitch; + } + + while (h--) { + colorFillClip<PixelType>(ptr, ptr + Base::_strokeWidth, color, x, ptr_y, _clippingArea); + colorFillClip<PixelType>(ptr + w - Base::_strokeWidth, ptr + w, color, x + w - Base::_strokeWidth, ptr_y, _clippingArea); + ptr += pitch; + ptr_y += 1; + } + } +} + /** SQUARE ALGORITHM **/ template<typename PixelType> void VectorRendererSpec<PixelType>:: @@ -1323,10 +2405,76 @@ drawBevelSquareAlg(int x, int y, int w, int h, int bevel, PixelType top_color, P } } +template<typename PixelType> +void VectorRendererSpec<PixelType>:: +drawBevelSquareAlgClip(int x, int y, int w, int h, int bevel, PixelType top_color, PixelType bottom_color, bool fill) { + int pitch = _activeSurface->pitch / _activeSurface->format.bytesPerPixel; + int i, j; + PixelType *ptr_left; + int ptr_x, ptr_y; + + // Fill Background + ptr_left = (PixelType *)_activeSurface->getBasePtr(x, y); + ptr_x = x; ptr_y = y; + i = h; + if (fill) { + assert((_bgColor & ~_alphaMask) == 0); // only support black + while (i--) { + darkenFillClip(ptr_left, ptr_left + w, ptr_x, ptr_y); + ptr_left += pitch; + ++ptr_y; + } + } + + x = MAX(x - bevel, 0); + y = MAX(y - bevel, 0); + + w = MIN(w + (bevel * 2), (int)_activeSurface->w); + h = MIN(h + (bevel * 2), (int)_activeSurface->h); + + ptr_left = (PixelType *)_activeSurface->getBasePtr(x, y); + ptr_x = x; ptr_y = y; + i = bevel; + while (i--) { + colorFillClip<PixelType>(ptr_left, ptr_left + w, top_color, ptr_x, ptr_y, _clippingArea); + ptr_left += pitch; + ++ptr_y; + } + + ptr_left = (PixelType *)_activeSurface->getBasePtr(x, y + bevel); + ptr_x = x; ptr_y = y + bevel; + i = h - bevel; + while (i--) { + colorFillClip<PixelType>(ptr_left, ptr_left + bevel, top_color, ptr_x, ptr_y, _clippingArea); + ptr_left += pitch; + ++ptr_y; + } + + ptr_left = (PixelType *)_activeSurface->getBasePtr(x, y + h - bevel); + ptr_x = x; ptr_y = y + h - bevel; + i = bevel; + while (i--) { + colorFillClip<PixelType>(ptr_left + i, ptr_left + w, bottom_color, ptr_x + i, ptr_y, _clippingArea); + ptr_left += pitch; + ++ptr_y; + } + + ptr_left = (PixelType *)_activeSurface->getBasePtr(x + w - bevel, y); + ptr_x = x + w - bevel; ptr_y = y; + i = h - bevel; + j = bevel - 1; + while (i--) { + colorFillClip<PixelType>(ptr_left + j, ptr_left + bevel, bottom_color, ptr_x + j, ptr_y, _clippingArea); + if (j > 0) j--; + ptr_left += pitch; + ++ptr_y; + } +} + /** GENERIC LINE ALGORITHM **/ template<typename PixelType> void VectorRendererSpec<PixelType>:: -drawLineAlg(int x1, int y1, int x2, int y2, int dx, int dy, PixelType color) { +drawLineAlg(int x1, int y1, int x2, int y2, uint dx, uint dy, PixelType color) { PixelType *ptr = (PixelType *)_activeSurface->getBasePtr(x1, y1); int pitch = _activeSurface->pitch / _activeSurface->format.bytesPerPixel; int xdir = (x2 > x1) ? 1 : -1; @@ -1371,6 +2519,59 @@ drawLineAlg(int x1, int y1, int x2, int y2, int dx, int dy, PixelType color) { *ptr = (PixelType)color; } +template<typename PixelType> +void VectorRendererSpec<PixelType>:: +drawLineAlgClip(int x1, int y1, int x2, int y2, uint dx, uint dy, PixelType color) { + PixelType *ptr = (PixelType *)_activeSurface->getBasePtr(x1, y1); + int pitch = _activeSurface->pitch / _activeSurface->format.bytesPerPixel; + int xdir = (x2 > x1) ? 1 : -1; + int ptr_x = x1, ptr_y = y1; + + if (IS_IN_CLIP(ptr_x, ptr_y)) *ptr = (PixelType)color; + + if (dx > dy) { + int ddy = dy * 2; + int dysub = ddy - (dx * 2); + int error_term = ddy - dx; + + while (dx--) { + if (error_term >= 0) { + ptr += pitch; + ++ptr_y; + error_term += dysub; + } else { + error_term += ddy; + } + + ptr += xdir; + ptr_x += xdir; + if (IS_IN_CLIP(ptr_x, ptr_y)) *ptr = (PixelType)color; + } + } else { + int ddx = dx * 2; + int dxsub = ddx - (dy * 2); + int error_term = ddx - dy; + + while (dy--) { + if (error_term >= 0) { + ptr += xdir; + ptr_x += xdir; + error_term += dxsub; + } else { + error_term += ddx; + } + + ptr += pitch; + ++ptr_y; + if (IS_IN_CLIP(ptr_x, ptr_y)) *ptr = (PixelType)color; + } + } + + ptr = (PixelType *)_activeSurface->getBasePtr(x2, y2); + ptr_x = x2; ptr_y = y2; + if (IS_IN_CLIP(ptr_x, ptr_y)) *ptr = (PixelType)color; +} + /** VERTICAL TRIANGLE DRAWING ALGORITHM **/ /** FIXED POINT ARITHMETIC @@ -1393,6 +2594,12 @@ drawLineAlg(int x1, int y1, int x2, int y2, int dx, int dy, PixelType color) { template<typename PixelType> void VectorRendererSpec<PixelType>:: drawTriangleVertAlg(int x1, int y1, int w, int h, bool inverted, PixelType color, VectorRenderer::FillMode fill_m) { + // Don't draw anything for empty rects. This assures dy is always different + // from zero. + if (w <= 0 || h <= 0) { + return; + } + int pitch = _activeSurface->pitch / _activeSurface->format.bytesPerPixel; int gradient_h = 0; if (!inverted) { @@ -1422,6 +2629,9 @@ drawTriangleVertAlg(int x1, int y1, int w, int h, bool inverted, PixelType color blendPixelPtr(floor, color, 50); #if FIXED_POINT + // In this branch dx is always different from zero. This is because + // abs(dx) is strictly greater than abs(dy), and abs returns zero + // as minimal value. int gradient = (dy << 8) / dx; int intery = (y1 << 8) + gradient; #else @@ -1560,10 +2770,221 @@ drawTriangleVertAlg(int x1, int y1, int w, int h, bool inverted, PixelType color } +///////////// + +template<typename PixelType> +void VectorRendererSpec<PixelType>:: +drawTriangleVertAlgClip(int x1, int y1, int w, int h, bool inverted, PixelType color, VectorRenderer::FillMode fill_m) { + // Don't draw anything for empty rects. This assures dy is always different + // from zero. + if (w <= 0 || h <= 0) { + return; + } + + int pitch = _activeSurface->pitch / _activeSurface->format.bytesPerPixel; + int gradient_h = 0; + int y_pitch_sign = 1; + if (!inverted) { + pitch = -pitch; + y1 += h; + y_pitch_sign = -1; + } + + PixelType *ptr_right = (PixelType *)_activeSurface->getBasePtr(x1, y1); + PixelType *floor = ptr_right - 1; + PixelType *ptr_left = (PixelType *)_activeSurface->getBasePtr(x1 + w, y1); + + int x2 = x1 + w / 2; + int y2 = y1 + h; + int x_right = x1; + int y_right = y1; + int x_left = x1 + w; + int y_left = y1; + int x_floor = x_right - 1; + int y_floor = y_right; + +#if FIXED_POINT + int dx = (x2 - x1) << 8; + int dy = (y2 - y1) << 8; + + if (abs(dx) > abs(dy)) { +#else + double dx = (double)x2 - (double)x1; + double dy = (double)y2 - (double)y1; + + if (fabs(dx) > fabs(dy)) { +#endif + while (floor++ != ptr_left) + blendPixelPtrClip(floor, color, 50, ++x_floor, y_floor); + +#if FIXED_POINT + // In this branch dx is always different from zero. This is because + // abs(dx) is strictly greater than abs(dy), and abs returns zero + // as minimal value. + int gradient = (dy << 8) / dx; + int intery = (y1 << 8) + gradient; +#else + double gradient = dy / dx; + double intery = y1 + gradient; +#endif + + for (int x = x1 + 1; x < x2; x++) { +#if FIXED_POINT + if (intery + gradient > ipart(intery) + 0x100) { +#else + if (intery + gradient > ipart(intery) + 1) { +#endif + ptr_right++; + ptr_left--; + ++x_right; + --x_left; + } + + ptr_left += pitch; + ptr_right += pitch; + y_right += y_pitch_sign; + y_left += y_pitch_sign; + + intery += gradient; + + switch (fill_m) { + case kFillDisabled: + if (IS_IN_CLIP(x_left, y_left)) *ptr_left = color; + if (IS_IN_CLIP(x_right, y_right)) *ptr_right = color; + break; + case kFillForeground: + case kFillBackground: + colorFillClip<PixelType>(ptr_right + 1, ptr_left, color, x_right+1, y_right, _clippingArea); + blendPixelPtrClip(ptr_right, color, rfpart(intery), x_right, y_right); + blendPixelPtrClip(ptr_left, color, rfpart(intery), x_left, y_left); + break; + case kFillGradient: + colorFillClip<PixelType>(ptr_right, ptr_left, calcGradient(gradient_h++, h), x_right, y_right, _clippingArea); + blendPixelPtrClip(ptr_right, color, rfpart(intery), x_right, y_right); + blendPixelPtrClip(ptr_left, color, rfpart(intery), x_left, y_left); + break; + } + } + + return; + } + +#if FIXED_POINT + if (abs(dx) < abs(dy)) { +#else + if (fabs(dx) < fabs(dy)) { +#endif + ptr_left--; + --x_left; + while (floor++ != ptr_left) + blendPixelPtrClip(floor, color, 50, ++x_floor, y_floor); + +#if FIXED_POINT + int gradient = (dx << 8) / (dy + 0x100); + int interx = (x1 << 8) + gradient; +#else + double gradient = dx / (dy + 1); + double interx = x1 + gradient; +#endif + + for (int y = y1 + 1; y < y2; y++) { +#if FIXED_POINT + if (interx + gradient > ipart(interx) + 0x100) { +#else + if (interx + gradient > ipart(interx) + 1) { +#endif + ptr_right++; + ptr_left--; + ++x_right; + --x_left; + } + + ptr_left += pitch; + ptr_right += pitch; + y_right += y_pitch_sign; + y_left += y_pitch_sign; + + interx += gradient; + + switch (fill_m) { + case kFillDisabled: + if (IS_IN_CLIP(x_left, y_left)) *ptr_left = color; + if (IS_IN_CLIP(x_right, y_right)) *ptr_right = color; + break; + case kFillForeground: + case kFillBackground: + colorFillClip<PixelType>(ptr_right + 1, ptr_left, color, x_right+1, y_right, _clippingArea); + blendPixelPtrClip(ptr_right, color, rfpart(interx), x_right, y_right); + blendPixelPtrClip(ptr_left, color, rfpart(interx), x_left, y_left); + break; + case kFillGradient: + colorFillClip<PixelType>(ptr_right, ptr_left, calcGradient(gradient_h++, h), x_right, y_right, _clippingArea); + blendPixelPtrClip(ptr_right, color, rfpart(interx), x_right, y_right); + blendPixelPtrClip(ptr_left, color, rfpart(interx), x_left, y_left); + break; + } + } + + return; + } + + ptr_left--; + --x_left; + while (floor++ != ptr_left) + blendPixelPtrClip(floor, color, 50, ++x_floor, y_floor); + +#if FIXED_POINT + int gradient = (dx / dy) << 8; + int interx = (x1 << 8) + gradient; +#else + double gradient = dx / dy; + double interx = x1 + gradient; +#endif + + for (int y = y1 + 1; y < y2; y++) { + ptr_right++; + ptr_left--; + ++x_right; + --x_left; + + ptr_left += pitch; + ptr_right += pitch; + y_right += y_pitch_sign; + y_left += y_pitch_sign; + + interx += gradient; + + switch (fill_m) { + case kFillDisabled: + if (IS_IN_CLIP(x_left, y_left)) *ptr_left = color; + if (IS_IN_CLIP(x_right, y_right)) *ptr_right = color; + break; + case kFillForeground: + case kFillBackground: + colorFillClip<PixelType>(ptr_right + 1, ptr_left, color, x_right+1, y_right, _clippingArea); + blendPixelPtrClip(ptr_right, color, rfpart(interx), x_right, y_right); + blendPixelPtrClip(ptr_left, color, rfpart(interx), x_left, y_left); + break; + case kFillGradient: + colorFillClip<PixelType>(ptr_right, ptr_left, calcGradient(gradient_h++, h), x_right, y_right, _clippingArea); + blendPixelPtrClip(ptr_right, color, rfpart(interx), x_right, y_right); + blendPixelPtrClip(ptr_left, color, rfpart(interx), x_left, y_left); + break; + } + } +} + +///////////// + /** VERTICAL TRIANGLE DRAWING - FAST VERSION FOR SQUARED TRIANGLES */ template<typename PixelType> void VectorRendererSpec<PixelType>:: drawTriangleFast(int x1, int y1, int size, bool inverted, PixelType color, VectorRenderer::FillMode fill_m) { + // Do not draw anything for empty rects. + if (size <= 0) { + return; + } + int pitch = _activeSurface->pitch / _activeSurface->format.bytesPerPixel; if (!inverted) { @@ -1657,6 +3078,9 @@ drawBorderRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType color, while (x++ < (y - 2)) { BE_ALGORITHM(); + if (x < _clippingArea.left || x > _clippingArea.right) continue; + if (y < _clippingArea.top || y > _clippingArea.bottom) continue; + 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))); @@ -1684,7 +3108,80 @@ drawBorderRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType color, template<typename PixelType> void VectorRendererSpec<PixelType>:: +drawBorderRoundedSquareAlgClip(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; + + 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; + + PixelType color1 = color; + PixelType color2 = color; + + while (sw++ < Base::_strokeWidth) { + blendFillClip(ptr_fill + sp + r, ptr_fill + w + 1 + sp - r, color1, alpha_t, + x1 + r, y1 + sp/pitch); // top + blendFillClip(ptr_fill + hp - sp + r, ptr_fill + w + hp + 1 - sp - r, color2, alpha_b, + x1 + r, y1 + (hp - sp)/ pitch); // bottom + sp += pitch; + + 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(); + + BE_DRAWCIRCLE_BCOLOR_TR_CW_CLIP(ptr_tr, x, y, px, py, (uint8)(alpha_r + (alphaStep_tr * x)), x1 + w - r, y1 + r); + BE_DRAWCIRCLE_BCOLOR_BR_CW_CLIP(ptr_br, x, y, px, py, (uint8)(alpha_b + (alphaStep_br * x)), x1 + w - r, y1 + h - r); + BE_DRAWCIRCLE_BCOLOR_BL_CW_CLIP(ptr_bl, x, y, px, py, (uint8)(alpha_l + (alphaStep_bl * x)), x1 + r, y1 + h - r); + BE_DRAWCIRCLE_BCOLOR_TL_CW_CLIP(ptr_tl, x, y, px, py, (uint8)(alpha_t + (alphaStep_tl * x)), x1 + r, y1 + r); + + BE_DRAWCIRCLE_BCOLOR_TR_CCW_CLIP(ptr_tr, x, y, px, py, (uint8)(alpha_t - (alphaStep_tr * x)), x1 + w - r, y1 + r); + BE_DRAWCIRCLE_BCOLOR_BR_CCW_CLIP(ptr_br, x, y, px, py, (uint8)(alpha_r - (alphaStep_br * x)), x1 + w - r, y1 + h - r); + BE_DRAWCIRCLE_BCOLOR_BL_CCW_CLIP(ptr_bl, x, y, px, py, (uint8)(alpha_b - (alphaStep_bl * x)), x1 + r, y1 + h - r); + BE_DRAWCIRCLE_BCOLOR_TL_CCW_CLIP(ptr_tl, x, y, px, py, (uint8)(alpha_l - (alphaStep_tl * x)), x1 + r, y1 + r); + + if (Base::_strokeWidth > 1) { + BE_DRAWCIRCLE_BCOLOR_CLIP(ptr_tr, ptr_tl, ptr_bl, ptr_br, x - 1, y, px, py, + x1 + w - r, y1 + r, x1 + r, y1 + r, x1 + r, y1 + h - r, x1 + w - r, y1 + h - r); + BE_DRAWCIRCLE_BCOLOR_CLIP(ptr_tr, ptr_tl, ptr_bl, ptr_br, x, y, px - pitch, py, + x1 + w - r, y1 + r, x1 + r, y1 + r, x1 + r, y1 + h - r, x1 + w - r, y1 + h - r); + } + } + } + + ptr_fill += pitch * real_radius; + while (short_h--) { + blendFillClip(ptr_fill, ptr_fill + Base::_strokeWidth, color1, alpha_l, + x1, y1 + real_radius + h - (2 * r) + 2 - short_h - 1); // left + blendFillClip(ptr_fill + w - Base::_strokeWidth + 1, ptr_fill + w + 1, color2, alpha_r, + x1 + w - Base::_strokeWidth + 1, y1 + real_radius + h - (2 * r) + 2 - short_h - 1); // right + ptr_fill += pitch; + } +} + +template<typename PixelType> +void VectorRendererSpec<PixelType>:: drawInteriorRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType color, VectorRenderer::FillMode fill_m) { + // Do not draw empty space rounded squares. + if (w <= 0 || h <= 0) { + return; + } + int f, ddF_x, ddF_y; int x, y, px, py; int pitch = _activeSurface->pitch / _activeSurface->format.bytesPerPixel; @@ -1710,6 +3207,8 @@ drawInteriorRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType colo while (x++ < y) { BE_ALGORITHM(); + if (y1 + r + y < _clippingArea.top || y1 + r + y > _clippingArea.bottom) continue; + color1 = calcGradient(real_radius - x, long_h); color2 = calcGradient(real_radius - y, long_h); color3 = calcGradient(long_h - r + x, long_h); @@ -1751,6 +3250,91 @@ drawInteriorRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType colo template<typename PixelType> void VectorRendererSpec<PixelType>:: +drawInteriorRoundedSquareAlgClip(int x1, int y1, int r, int w, int h, PixelType color, VectorRenderer::FillMode fill_m) { + // Do not draw empty space rounded squares. + if (w <= 0 || h <= 0) { + return; + } + + int f, ddF_x, ddF_y; + int x, y, px, py; + int pitch = _activeSurface->pitch / _activeSurface->format.bytesPerPixel; + + 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; + + BE_RESET(); + + PixelType color1 = color; + + if (fill_m == kFillGradient) { + PixelType color2, color3, color4; + precalcGradient(long_h); + + 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); + + //TL = (x1 + r, y1 + r) + gradientFillClip(ptr_tl - x - py, w - 2 * r + 2 * x, x1 + r - x - y, real_radius - y, + x1 + r - x, y1 + r - y); + gradientFillClip(ptr_tl - y - px, w - 2 * r + 2 * y, x1 + r - y - x, real_radius - x, + x1 + r - y, y1 + r - x); + + //BL = (x1 + r, y1 + h - r) + gradientFillClip(ptr_bl - x + py, w - 2 * r + 2 * x, x1 + r - x - y, long_h - r + y, + x1 + r - x, y1 + h - r + y); + gradientFillClip(ptr_bl - y + px, w - 2 * r + 2 * y, x1 + r - y - x, long_h - r + x, + x1 + r - y, y1 + h - r + x); + + BE_DRAWCIRCLE_XCOLOR_CLIP(ptr_tr, ptr_tl, ptr_bl, ptr_br, x, y, px, py, + x1 + w - r, y1 + r, x1 + r, y1 + r, x1 + r, y1 + h - r, x1 + w - r, y1 + h - r); + } + } else { + while (x++ < y) { + BE_ALGORITHM(); + + colorFillClip<PixelType>(ptr_tl - x - py, ptr_tr + x - py, color1, + x1 + r - x, y1 + r - y, _clippingArea); + colorFillClip<PixelType>(ptr_tl - y - px, ptr_tr + y - px, color1, + x1 + r - y, y1 + r - x, _clippingArea); + + colorFillClip<PixelType>(ptr_bl - x + py, ptr_br + x + py, color1, + x1 + r - x, y1 + h - r + y, _clippingArea); + colorFillClip<PixelType>(ptr_bl - y + px, ptr_br + y + px, color1, + x1 + r - y, y1 + h - r + x, _clippingArea); + + // do not remove - messes up the drawing at lower resolutions + BE_DRAWCIRCLE_CLIP(ptr_tr, ptr_tl, ptr_bl, ptr_br, x, y, px, py, + x1 + w - r, y1 + r, x1 + r, y1 + r, x1 + r, y1 + h - r, x1 + w - r, y1 + h - r); + } + } + + ptr_fill += pitch * r; + int short_h_orig = short_h; + while (short_h--) { + if (fill_m == kFillGradient) { + gradientFillClip(ptr_fill, w + 1, x1, real_radius++, x1, y1 + r + short_h_orig - short_h -1); + } else { + colorFillClip<PixelType>(ptr_fill, ptr_fill + w + 1, color1, x1, y1 + r + short_h_orig - short_h - 1, _clippingArea); + } + 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; @@ -1780,6 +3364,38 @@ drawRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType color, Vecto } } +template<typename PixelType> +void VectorRendererSpec<PixelType>:: +drawRoundedSquareAlgClip(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) + drawInteriorRoundedSquareAlgClip(x1, y1, r, w, h, _bgColor, fill_m); + else + drawInteriorRoundedSquareAlgClip(x1, y1, r, w, h, color, fill_m); + } + + //I expect these to work fine with clipping: + if (Base::_strokeWidth) { + if (r != 0 && _bevel > 0) { + drawBorderRoundedSquareAlgClip(x1, y1, r, w, h, color, fill_m, borderAlpha_t, borderAlpha_r, borderAlpha_b, borderAlpha_l); + drawBorderRoundedSquareAlgClip(x1, y1, r, w, h, _bevelColor, fill_m, bevelAlpha_t, bevelAlpha_r, bevelAlpha_b, bevelAlpha_l); + } else { + drawBorderRoundedSquareAlgClip(x1, y1, r, w, h, color, fill_m, 255, 255, 255, 255); + } + } +} + /** CIRCLE ALGORITHM **/ template<typename PixelType> void VectorRendererSpec<PixelType>:: @@ -1824,7 +3440,47 @@ drawCircleAlg(int x1, int y1, int r, PixelType color, VectorRenderer::FillMode f } +template<typename PixelType> +void VectorRendererSpec<PixelType>:: +drawCircleAlgClip(int x1, int y1, int r, PixelType color, VectorRenderer::FillMode fill_m) { + int f, ddF_x, ddF_y; + int x, y, px, py, sw = 0; + int pitch = _activeSurface->pitch / _activeSurface->format.bytesPerPixel; + PixelType *ptr = (PixelType *)Base::_activeSurface->getBasePtr(x1, y1); + + if (fill_m == kFillDisabled) { + while (sw++ < Base::_strokeWidth) { + BE_RESET(); + r--; + + if (IS_IN_CLIP(x1 + y, y1)) *(ptr + y) = color; + if (IS_IN_CLIP(x1 - y, y1)) *(ptr - y) = color; + if (IS_IN_CLIP(x1, y1 + y)) *(ptr + py) = color; + if (IS_IN_CLIP(x1, y1 - y)) *(ptr - py) = color; + + while (x++ < y) { + BE_ALGORITHM(); + BE_DRAWCIRCLE_CLIP(ptr, ptr, ptr, ptr, x, y, px, py, x1, y1, x1, y1, x1, y1, x1, y1); + + if (Base::_strokeWidth > 1) { + BE_DRAWCIRCLE_CLIP(ptr, ptr, ptr, ptr, x - 1, y, px, py, x1, y1, x1, y1, x1, y1, x1, y1); + BE_DRAWCIRCLE_CLIP(ptr, ptr, ptr, ptr, x, y, px - pitch, py, x1, y1, x1, y1, x1, y1, x1, y1); + } + } + } + } else { + colorFillClip<PixelType>(ptr - r, ptr + r, color, x1 - r, y1 + r, _clippingArea); + BE_RESET(); + while (x++ < y) { + BE_ALGORITHM(); + colorFillClip<PixelType>(ptr - x + py, ptr + x + py, color, x1 - x, y1 + y, _clippingArea); + colorFillClip<PixelType>(ptr - x - py, ptr + x - py, color, x1 - x, y1 - y, _clippingArea); + colorFillClip<PixelType>(ptr - y + px, ptr + y + px, color, x1 - y, y1 + x, _clippingArea); + colorFillClip<PixelType>(ptr - y - px, ptr + y - px, color, x1 - y, y1 - x, _clippingArea); + } + } +} /******************************************************************** @@ -1835,6 +3491,11 @@ 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 offset) { + // Do nothing for empty rects or no shadow offset. + if (w <= 0 || h <= 0 || offset <= 0) { + return; + } + PixelType *ptr = (PixelType *)_activeSurface->getBasePtr(x + w - 1, y + offset); int pitch = _activeSurface->pitch / _activeSurface->format.bytesPerPixel; int i, j; @@ -1870,6 +3531,54 @@ drawSquareShadow(int x, int y, int w, int h, int offset) { template<typename PixelType> void VectorRendererSpec<PixelType>:: +drawSquareShadowClip(int x, int y, int w, int h, int offset) { + // Do nothing for empty rects or no shadow offset. + if (w <= 0 || h <= 0 || offset <= 0) { + return; + } + + PixelType *ptr = (PixelType *)_activeSurface->getBasePtr(x + w - 1, y + offset); + int pitch = _activeSurface->pitch / _activeSurface->format.bytesPerPixel; + int i, j, ptr_x = x+w-1, ptr_y = y+offset; + + i = h - offset; + + while (i--) { + j = offset; + while (j--) + blendPixelPtrClip(ptr + j, 0, ((offset - j) << 8) / offset, ptr_x + j, ptr_y); + ptr += pitch; + ++ptr_y; + } + + ptr = (PixelType *)_activeSurface->getBasePtr(x + offset, y + h - 1); + ptr_x = x + offset; + ptr_y = y + h - 1; + + while (i++ < offset) { + j = w - offset; + while (j--) + blendPixelPtrClip(ptr + j, 0, ((offset - i) << 8) / offset, ptr_x + j, ptr_y); + ptr += pitch; + ++ptr_y; + } + + ptr = (PixelType *)_activeSurface->getBasePtr(x + w, y + h); + ptr_x = x + w; + ptr_y = y + h; + + i = 0; + while (i++ < offset) { + j = offset - 1; + while (j--) + blendPixelPtrClip(ptr + j, 0, (((offset - j) * (offset - i)) << 8) / (offset * offset), ptr_x + j, ptr_y); + ptr += pitch; + ++ptr_y; + } +} + +template<typename PixelType> +void VectorRendererSpec<PixelType>:: drawRoundedSquareShadow(int x1, int y1, int r, int w, int h, int offset) { int pitch = _activeSurface->pitch / _activeSurface->format.bytesPerPixel; @@ -1907,7 +3616,6 @@ drawRoundedSquareShadow(int x1, int y1, int r, int w, int h, int offset) { while (x++ < y) { BE_ALGORITHM(); - if (((1 << x) & hb) == 0) { blendFill(ptr_tl - y - px, ptr_tr + y - px, color, (uint8)alpha); @@ -1943,6 +3651,83 @@ drawRoundedSquareShadow(int x1, int y1, int r, int w, int h, int offset) { } } +template<typename PixelType> +void VectorRendererSpec<PixelType>:: +drawRoundedSquareShadowClip(int x1, int y1, int r, int w, int h, int offset) { + 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; + + // 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); + + 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) { + blendFillClip(ptr_tl - y - px, ptr_tr + y - px, color, (uint8)alpha, + xstart + r - y, ystart + r - x); + + // Will create a dark line of pixles if left out + if (hb > 0) { + blendFillClip(ptr_bl - y + px, ptr_br + y + px, color, (uint8)alpha, + xstart + r - y, ystart + height - r + x); + } + hb |= (1 << x); + } + + if (((1 << y) & hb) == 0) { + blendFillClip(ptr_tl - x - py, ptr_tr + x - py, color, (uint8)alpha, xstart + r - x, ystart + r - y); + blendFillClip(ptr_bl - x + py, ptr_br + x + py, color, (uint8)alpha, xstart + r - x, ystart + height - r + y); + hb |= (1 << y); + } + } + + ptr_fill += pitch * r; + int orig_short_h = short_h; + while (short_h--) { + blendFillClip(ptr_fill, ptr_fill + width + 1, color, (uint8)alpha, + xstart, ystart + r + orig_short_h - short_h - 1); + 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; + } +} /******************************************************************************/ @@ -1956,8 +3741,7 @@ drawRoundedSquareShadow(int x1, int y1, int r, int w, int h, int offset) { /** LINES **/ template<typename PixelType> void VectorRendererAA<PixelType>:: -drawLineAlg(int x1, int y1, int x2, int y2, int dx, int dy, PixelType color) { - +drawLineAlg(int x1, int y1, int x2, int y2, uint dx, uint dy, PixelType color) { PixelType *ptr = (PixelType *)Base::_activeSurface->getBasePtr(x1, y1); int pitch = Base::_activeSurface->pitch / Base::_activeSurface->format.bytesPerPixel; int xdir = (x2 > x1) ? 1 : -1; @@ -1967,7 +3751,7 @@ drawLineAlg(int x1, int y1, int x2, int y2, int dx, int dy, PixelType color) { *ptr = (PixelType)color; if (dx > dy) { - gradient = (uint32)(dy << 16) / (uint32)dx; + gradient = (dy << 16) / dx; error_acc = 0; while (--dx) { @@ -1983,8 +3767,8 @@ drawLineAlg(int x1, int y1, int x2, int y2, int dx, int dy, PixelType color) { this->blendPixelPtr(ptr, color, ~alpha); this->blendPixelPtr(ptr + pitch, color, alpha); } - } else { - gradient = (uint32)(dx << 16) / (uint32)dy; + } else if (dy != 0) { + gradient = (dx << 16) / dy; error_acc = 0; while (--dy) { @@ -2009,6 +3793,11 @@ drawLineAlg(int x1, int y1, int x2, int y2, int dx, int dy, PixelType color) { template<typename PixelType> void VectorRendererAA<PixelType>:: drawTabAlg(int x1, int y1, int w, int h, int r, PixelType color, VectorRenderer::FillMode fill_m, int baseLeft, int baseRight) { + // Don't draw anything for empty rects. + if (w <= 0 || h <= 0) { + return; + } + int x, y, px, py; int pitch = Base::_activeSurface->pitch / Base::_activeSurface->format.bytesPerPixel; int sw = 0, sp = 0, hp = 0; @@ -2217,6 +4006,14 @@ drawBorderRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType color, template<typename PixelType> void VectorRendererAA<PixelType>:: drawInteriorRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType color, VectorRenderer::FillMode fill_m) { + w -= 2*Base::_strokeWidth; + h -= 2*Base::_strokeWidth; + + // Do not draw empty space rounded squares. + if (w <= 0 || h <= 0) { + return; + } + int x, y; const int pitch = Base::_activeSurface->pitch / Base::_activeSurface->format.bytesPerPixel; int px, py; @@ -2228,8 +4025,6 @@ drawInteriorRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType colo r -= Base::_strokeWidth; x1 += Base::_strokeWidth; y1 += Base::_strokeWidth; - w -= 2*Base::_strokeWidth; - h -= 2*Base::_strokeWidth; rsq = r*r; PixelType *ptr_tl = (PixelType *)Base::_activeSurface->getBasePtr(x1 + r, y1 + r); diff --git a/graphics/VectorRendererSpec.h b/graphics/VectorRendererSpec.h index f47cc3997a..84c802f6df 100644 --- a/graphics/VectorRendererSpec.h +++ b/graphics/VectorRendererSpec.h @@ -51,14 +51,31 @@ public: VectorRendererSpec(PixelFormat format); void drawLine(int x1, int y1, int x2, int y2); + void drawLineClip(int x1, int y1, int x2, int y2, Common::Rect clipping); void drawCircle(int x, int y, int r); + void drawCircleClip(int x, int y, int r, Common::Rect clipping); void drawSquare(int x, int y, int w, int h); + void drawSquareClip(int x, int y, int w, int h, Common::Rect clipping); void drawRoundedSquare(int x, int y, int r, int w, int h); + void drawRoundedSquareClip(int x, int y, int r, int w, int h, Common::Rect clipping); void drawTriangle(int x, int y, int base, int height, TriangleOrientation orient); + void drawTriangleClip(int x, int y, int base, int height, TriangleOrientation orient, Common::Rect clipping); void drawTab(int x, int y, int r, int w, int h); + void drawTabClip(int x, int y, int r, int w, int h, Common::Rect clipping); void drawBeveledSquare(int x, int y, int w, int h, int bevel) { drawBevelSquareAlg(x, y, w, h, bevel, _bevelColor, _fgColor, Base::_fillMode != kFillDisabled); } + void drawBeveledSquareClip(int x, int y, int w, int h, int bevel, Common::Rect clipping) { + bool useClippingVersions = !(clipping.isEmpty() || clipping.contains(Common::Rect(x, y, x + w, y + h))); + if (useClippingVersions) { + Common::Rect backup = _clippingArea; + _clippingArea = clipping; + drawBevelSquareAlgClip(x, y, w, h, bevel, _bevelColor, _fgColor, Base::_fillMode != kFillDisabled); + _clippingArea = backup; + } else { + drawBevelSquareAlg(x, y, w, h, bevel, _bevelColor, _fgColor, Base::_fillMode != kFillDisabled); + } + } 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, const Common::Rect &textDrawableArea = Common::Rect(0, 0, 0, 0)); @@ -72,14 +89,24 @@ public: void copyWholeFrame(OSystem *sys) { copyFrame(sys, Common::Rect(0, 0, _activeSurface->w, _activeSurface->h)); } void fillSurface(); + void fillSurfaceClip(Common::Rect clipping); void blitSurface(const Graphics::Surface *source, const Common::Rect &r); void blitSubSurface(const Graphics::Surface *source, const Common::Rect &r); - void blitAlphaBitmap(const Graphics::Surface *source, const Common::Rect &r); + void blitSubSurfaceClip(const Graphics::Surface *source, const Common::Rect &r, const Common::Rect &clipping); + void blitKeyBitmap(const Graphics::Surface *source, const Common::Rect &r); + void blitKeyBitmapClip(const Graphics::Surface *source, const Common::Rect &r, const Common::Rect &clipping); + void blitAlphaBitmap(Graphics::TransparentSurface *source, const Common::Rect &r, + GUI::ThemeEngine::AutoScaleMode autoscale = GUI::ThemeEngine::kAutoScaleNone, + Graphics::DrawStep::VectorAlignment xAlign = Graphics::DrawStep::kVectorAlignManual, + Graphics::DrawStep::VectorAlignment yAlign = Graphics::DrawStep::kVectorAlignManual, + int alpha = 255); void applyScreenShading(GUI::ThemeEngine::ShadingStyle shadingStyle); protected: + Common::Rect _clippingArea; + /** * Draws a single pixel on the surface with the given coordinates and * the given color. @@ -119,6 +146,7 @@ protected: * @param alpha Alpha intensity of the pixel (0-255) */ inline void blendPixelPtr(PixelType *ptr, PixelType color, uint8 alpha); + inline void blendPixelPtrClip(PixelType *ptr, PixelType color, uint8 alpha, int x, int y); /** * Blends a single pixel on the surface in the given pixel pointer, using supplied color @@ -150,42 +178,76 @@ protected: * @see VectorRendererAA::drawCircleAlg */ virtual void drawLineAlg(int x1, int y1, int x2, int y2, - int dx, int dy, PixelType color); + uint dx, uint dy, PixelType color); + + virtual void drawLineAlgClip(int x1, int y1, int x2, int y2, + uint dx, uint dy, PixelType color); virtual void drawCircleAlg(int x, int y, int r, PixelType color, FillMode fill_m); + virtual void drawCircleAlgClip(int x, int y, int r, + PixelType color, FillMode fill_m); + virtual void drawRoundedSquareAlg(int x1, int y1, int r, int w, int h, PixelType color, FillMode fill_m); + virtual void drawRoundedSquareAlgClip(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 drawBorderRoundedSquareAlgClip(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 drawInteriorRoundedSquareAlgClip(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); + virtual void drawSquareAlgClip(int x, int y, int w, int h, + PixelType color, FillMode fill_m); + virtual void drawTriangleVertAlg(int x, int y, int w, int h, bool inverted, PixelType color, FillMode fill_m); + virtual void drawTriangleVertAlgClip(int x, int y, int w, int h, + bool inverted, PixelType color, FillMode fill_m); + virtual void drawTriangleFast(int x, int y, int size, bool inverted, PixelType color, FillMode fill_m); virtual void drawBevelSquareAlg(int x, int y, int w, int h, int bevel, PixelType top_color, PixelType bottom_color, bool fill); + virtual void drawBevelSquareAlgClip(int x, int y, int w, int h, + int bevel, PixelType top_color, PixelType bottom_color, bool fill); + virtual void drawTabAlg(int x, int y, int w, int h, int r, PixelType color, VectorRenderer::FillMode fill_m, int baseLeft = 0, int baseRight = 0); + virtual void drawTabAlgClip(int x, int y, int w, int h, int r, + 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 drawTabShadowClip(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); + virtual void drawBevelTabAlgClip(int x, int y, int w, int h, + int bevel, PixelType topColor, PixelType bottomColor, + int baseLeft = 0, int baseRight = 0); + /** * SHADOW DRAWING ALGORITHMS * @@ -197,7 +259,9 @@ protected: * @param offset Intensity/size of the shadow. */ virtual void drawSquareShadow(int x, int y, int w, int h, int offset); + virtual void drawSquareShadowClip(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); + virtual void drawRoundedSquareShadowClip(int x, int y, int r, int w, int h, int offset); /** * Calculates the color gradient on a given point. @@ -212,6 +276,7 @@ protected: void precalcGradient(int h); void gradientFill(PixelType *first, int width, int x, int y); + void gradientFillClip(PixelType *first, int width, int x, int y, int realX, int realY); /** * Fills several pixels in a row with a given color and the specified alpha blending. @@ -227,7 +292,20 @@ protected: while (first != last) blendPixelPtr(first++, color, alpha); } + inline void blendFillClip(PixelType *first, PixelType *last, PixelType color, uint8 alpha, int realX, int realY) { + if (_clippingArea.top <= realY && realY < _clippingArea.bottom) { + while (first != last) { + if (_clippingArea.left <= realX && realX < _clippingArea.right) + blendPixelPtr(first++, color, alpha); + else + ++first; + ++realX; + } + } + } + void darkenFill(PixelType *first, PixelType *last); + void darkenFillClip(PixelType *first, PixelType *last, int x, int y); const PixelFormat _format; const PixelType _redMask, _greenMask, _blueMask, _alphaMask; @@ -278,7 +356,7 @@ protected: * * @see VectorRenderer::drawLineAlg() */ - virtual void drawLineAlg(int x1, int y1, int x2, int y2, int dx, int dy, PixelType color); + virtual void drawLineAlg(int x1, int y1, int x2, int y2, uint dx, uint dy, PixelType color); /** * "Wu's Circle Antialiasing Algorithm" as published by Xiaolin Wu, July 1991 diff --git a/graphics/font.cpp b/graphics/font.cpp index dba48249bc..97662dc15d 100644 --- a/graphics/font.cpp +++ b/graphics/font.cpp @@ -21,6 +21,7 @@ */ #include "graphics/font.h" +#include "graphics/managed_surface.h" #include "common/array.h" #include "common/util.h" @@ -264,6 +265,14 @@ int Font::getStringWidth(const Common::U32String &str) const { return getStringWidthImpl(*this, str); } +void Font::drawChar(ManagedSurface *dst, uint32 chr, int x, int y, uint32 color) const { + drawChar(&dst->_innerSurface, chr, x, y, color); + + Common::Rect charBox = getBoundingBox(chr); + charBox.translate(x, y); + dst->addDirtyRect(charBox); +} + void Font::drawString(Surface *dst, const Common::String &str, int x, int y, int w, uint32 color, TextAlign align, int deltax, bool useEllipsis) const { Common::String renderStr = useEllipsis ? handleEllipsis(str, w) : str; drawStringImpl(*this, dst, renderStr, x, y, w, color, align, deltax); @@ -273,6 +282,20 @@ void Font::drawString(Surface *dst, const Common::U32String &str, int x, int y, drawStringImpl(*this, dst, str, x, y, w, color, align, 0); } +void Font::drawString(ManagedSurface *dst, const Common::String &str, int x, int y, int w, uint32 color, TextAlign align, int deltax, bool useEllipsis) const { + drawString(&dst->_innerSurface, str, x, y, w, color, align, deltax, useEllipsis); + if (w != 0) { + dst->addDirtyRect(getBoundingBox(str, x, y, w, align, deltax, useEllipsis)); + } +} + +void Font::drawString(ManagedSurface *dst, const Common::U32String &str, int x, int y, int w, uint32 color, TextAlign align) const { + drawString(&dst->_innerSurface, str, x, y, w, color, align); + if (w != 0) { + dst->addDirtyRect(getBoundingBox(str, x, y, w, align)); + } +} + int Font::wordWrapText(const Common::String &str, int maxWidth, Common::Array<Common::String> &lines) const { return wordWrapTextImpl(*this, str, maxWidth, lines); } diff --git a/graphics/font.h b/graphics/font.h index 35f6792d7f..0478608708 100644 --- a/graphics/font.h +++ b/graphics/font.h @@ -34,6 +34,7 @@ template<class T> class Array; namespace Graphics { struct Surface; +class ManagedSurface; /** Text alignment modes */ enum TextAlign { @@ -141,10 +142,13 @@ public: * @param color The color of the character. */ virtual void drawChar(Surface *dst, uint32 chr, int x, int y, uint32 color) const = 0; + void drawChar(ManagedSurface *dst, uint32 chr, int x, int y, uint32 color) const; // 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; + void drawString(ManagedSurface *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(ManagedSurface *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. diff --git a/graphics/fonts/ttf.cpp b/graphics/fonts/ttf.cpp index ba57613601..76b7f731be 100644 --- a/graphics/fonts/ttf.cpp +++ b/graphics/fonts/ttf.cpp @@ -33,11 +33,15 @@ #include "common/singleton.h" #include "common/stream.h" +#include "common/memstream.h" #include "common/hashmap.h" +#include "common/ptr.h" #include <ft2build.h> #include FT_FREETYPE_H #include FT_GLYPH_H +#include FT_TRUETYPE_TABLES_H +#include FT_TRUETYPE_TAGS_H namespace Graphics { @@ -47,6 +51,10 @@ inline int ftCeil26_6(FT_Pos x) { return (x + 63) / 64; } +inline int divRoundToNearest(int dividend, int divisor) { + return (dividend + (divisor / 2)) / divisor; +} + } // End of anonymous namespace class TTFLibrary : public Common::Singleton<TTFLibrary> { @@ -101,7 +109,7 @@ public: TTFFont(); virtual ~TTFFont(); - bool load(Common::SeekableReadStream &stream, int size, uint dpi, TTFRenderMode renderMode, const uint32 *mapping); + bool load(Common::SeekableReadStream &stream, int size, TTFSizeMode sizeMode, uint dpi, TTFRenderMode renderMode, const uint32 *mapping); virtual int getFontHeight() const; @@ -137,6 +145,12 @@ private: bool _allowLateCaching; void assureCached(uint32 chr) const; + Common::SeekableReadStream *readTTFTable(FT_ULong tag) const; + + int computePointSize(int size, TTFSizeMode sizeMode) const; + int readPointSizeFromVDMXTable(int height) const; + int computePointSizeFromHeaders(int height) const; + FT_Int32 _loadFlags; FT_Render_Mode _renderMode; bool _hasKerning; @@ -162,7 +176,7 @@ TTFFont::~TTFFont() { } } -bool TTFFont::load(Common::SeekableReadStream &stream, int size, uint dpi, TTFRenderMode renderMode, const uint32 *mapping) { +bool TTFFont::load(Common::SeekableReadStream &stream, int size, TTFSizeMode sizeMode, uint dpi, TTFRenderMode renderMode, const uint32 *mapping) { if (!g_ttf.isInitialized()) return false; @@ -200,7 +214,7 @@ bool TTFFont::load(Common::SeekableReadStream &stream, int size, uint dpi, TTFRe // Check whether we have kerning support _hasKerning = (FT_HAS_KERNING(_face) != 0); - if (FT_Set_Char_Size(_face, 0, size * 64, dpi, dpi)) { + if (FT_Set_Char_Size(_face, 0, computePointSize(size, sizeMode) * 64, dpi, dpi)) { delete[] _ttfFile; _ttfFile = 0; @@ -262,6 +276,126 @@ bool TTFFont::load(Common::SeekableReadStream &stream, int size, uint dpi, TTFRe return _initialized; } +int TTFFont::computePointSize(int size, TTFSizeMode sizeMode) const { + int ptSize = 0; + switch (sizeMode) { + case kTTFSizeModeCell: { + ptSize = readPointSizeFromVDMXTable(size); + + if (ptSize == 0) { + ptSize = computePointSizeFromHeaders(size); + } + + if (ptSize == 0) { + warning("Unable to compute point size for font '%s'", _face->family_name); + ptSize = 1; + } + break; + } + case kTTFSizeModeCharacter: + ptSize = size; + break; + } + + return ptSize; +} + +Common::SeekableReadStream *TTFFont::readTTFTable(FT_ULong tag) const { + // Find the required buffer size by calling the load function with nullptr + FT_ULong size = 0; + FT_Error err = FT_Load_Sfnt_Table(_face, tag, 0, nullptr, &size); + if (err) { + return nullptr; + } + + byte *buf = (byte *)malloc(size); + if (!buf) { + return nullptr; + } + + err = FT_Load_Sfnt_Table(_face, tag, 0, buf, &size); + if (err) { + free(buf); + return nullptr; + } + + return new Common::MemoryReadStream(buf, size, DisposeAfterUse::YES); +} + +int TTFFont::readPointSizeFromVDMXTable(int height) const { + // The Vertical Device Metrics table matches font heights with point sizes. + // FreeType does not expose it, we have to parse it ourselves. + // See https://www.microsoft.com/typography/otspec/vdmx.htm + + Common::ScopedPtr<Common::SeekableReadStream> vdmxBuf(readTTFTable(TTAG_VDMX)); + if (!vdmxBuf) { + return 0; + } + + // Read the main header + vdmxBuf->skip(4); // Skip the version + uint16 numRatios = vdmxBuf->readUint16BE(); + + // Compute the starting position for the group table positions table + int32 offsetTableStart = vdmxBuf->pos() + 4 * numRatios; + + // Search the ratio table for the 1:1 ratio, or the default record (0, 0, 0) + int32 selectedRatio = -1; + for (uint16 i = 0; i < numRatios; i++) { + vdmxBuf->skip(1); // Skip the charset subset + uint8 xRatio = vdmxBuf->readByte(); + uint8 yRatio1 = vdmxBuf->readByte(); + uint8 yRatio2 = vdmxBuf->readByte(); + + if ((xRatio == 1 && yRatio1 <= 1 && yRatio2 >= 1) + || (xRatio == 0 && yRatio1 == 0 && yRatio2 == 0)) { + selectedRatio = i; + break; + } + } + if (selectedRatio < 0) { + return 0; + } + + // Read from group table positions table to get the group table offset + vdmxBuf->seek(offsetTableStart + sizeof(uint16) * selectedRatio); + uint16 groupOffset = vdmxBuf->readUint16BE(); + + // Read the group table header + vdmxBuf->seek(groupOffset); + uint16 numRecords = vdmxBuf->readUint16BE(); + vdmxBuf->skip(2); // Skip the table bounds + + // Search a record matching the required height + for (uint16 i = 0; i < numRecords; i++) { + uint16 pointSize = vdmxBuf->readUint16BE(); + int16 yMax = vdmxBuf->readSint16BE(); + int16 yMin = vdmxBuf->readSint16BE(); + + if (yMax + -yMin > height) { + return 0; + } + if (yMax + -yMin == height) { + return pointSize; + } + } + + return 0; +} + +int TTFFont::computePointSizeFromHeaders(int height) const { + TT_OS2 *os2Header = (TT_OS2 *)FT_Get_Sfnt_Table(_face, ft_sfnt_os2); + TT_HoriHeader *horiHeader = (TT_HoriHeader *)FT_Get_Sfnt_Table(_face, ft_sfnt_hhea); + + if (os2Header && (os2Header->usWinAscent + os2Header->usWinDescent != 0)) { + return divRoundToNearest(_face->units_per_EM * height, os2Header->usWinAscent + os2Header->usWinDescent); + } else if (horiHeader && (horiHeader->Ascender + horiHeader->Descender != 0)) { + return divRoundToNearest(_face->units_per_EM * height, horiHeader->Ascender + horiHeader->Descender); + } + + return 0; +} + int TTFFont::getFontHeight() const { return _height; } @@ -474,11 +608,11 @@ bool TTFFont::cacheGlyph(Glyph &glyph, uint32 chr) const { switch (bitmap.pixel_mode) { case FT_PIXEL_MODE_MONO: - for (int y = 0; y < bitmap.rows; ++y) { + for (int y = 0; y < (int)bitmap.rows; ++y) { const uint8 *curSrc = src; uint8 mask = 0; - for (int x = 0; x < bitmap.width; ++x) { + for (int x = 0; x < (int)bitmap.width; ++x) { if ((x % 8) == 0) mask = *curSrc++; @@ -494,7 +628,7 @@ bool TTFFont::cacheGlyph(Glyph &glyph, uint32 chr) const { break; case FT_PIXEL_MODE_GRAY: - for (int y = 0; y < bitmap.rows; ++y) { + for (int y = 0; y < (int)bitmap.rows; ++y) { memcpy(dst, src, bitmap.width); dst += glyph.image.pitch; src += srcPitch; @@ -521,10 +655,10 @@ void TTFFont::assureCached(uint32 chr) const { } } -Font *loadTTFFont(Common::SeekableReadStream &stream, int size, uint dpi, TTFRenderMode renderMode, const uint32 *mapping) { +Font *loadTTFFont(Common::SeekableReadStream &stream, int size, TTFSizeMode sizeMode, uint dpi, TTFRenderMode renderMode, const uint32 *mapping) { TTFFont *font = new TTFFont(); - if (!font->load(stream, size, dpi, renderMode, mapping)) { + if (!font->load(stream, size, sizeMode, dpi, renderMode, mapping)) { delete font; return 0; } diff --git a/graphics/fonts/ttf.h b/graphics/fonts/ttf.h index bd25b69f21..4110486357 100644 --- a/graphics/fonts/ttf.h +++ b/graphics/fonts/ttf.h @@ -56,10 +56,32 @@ enum TTFRenderMode { }; /** + * This specifies how the font size is defined. + */ +enum TTFSizeMode { + /** + * Character height only. + * + * This matches rendering obtained when calling + * CreateFont in Windows with negative height values. + */ + kTTFSizeModeCharacter, + + /** + * Full cell height. + * + * This matches rendering obtained when calling + * CreateFont in Windows with positive height values. + */ + kTTFSizeModeCell +}; + +/** * Loads a TTF font file from a given data stream object. * * @param stream Stream object to load font data from. * @param size The point size to load. + * @param sizeMode The point size definition used for the size parameter. * @param dpi The dpi to use for size calculations, by default 72dpi * are used. * @param renderMode FreeType2 mode used to render glyphs. @see TTFRenderMode @@ -71,7 +93,7 @@ enum TTFRenderMode { * supported. * @return 0 in case loading fails, otherwise a pointer to the Font object. */ -Font *loadTTFFont(Common::SeekableReadStream &stream, int size, uint dpi = 0, TTFRenderMode renderMode = kTTFRenderModeLight, const uint32 *mapping = 0); +Font *loadTTFFont(Common::SeekableReadStream &stream, int size, TTFSizeMode sizeMode = kTTFSizeModeCharacter, uint dpi = 0, TTFRenderMode renderMode = kTTFRenderModeLight, const uint32 *mapping = 0); void shutdownTTF(); diff --git a/graphics/macgui/macmenu.cpp b/graphics/macgui/macmenu.cpp new file mode 100644 index 0000000000..a78fa2e6ce --- /dev/null +++ b/graphics/macgui/macmenu.cpp @@ -0,0 +1,583 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * MIT License: + * + * Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "common/system.h" +#include "common/keyboard.h" + +#include "graphics/primitives.h" +#include "graphics/font.h" +#include "graphics/macgui/macwindowmanager.h" +#include "graphics/macgui/macwindow.h" +#include "graphics/macgui/macmenu.h" + +namespace Graphics { + +enum { + kMenuHeight = 20, + kMenuLeftMargin = 7, + kMenuSpacing = 13, + kMenuPadding = 16, + kMenuDropdownPadding = 14, + kMenuDropdownItemHeight = 16, + kMenuItemHeight = 20 +}; + +enum { + kMenuHighLevel = -1 +}; + +enum { + kFontStyleBold = 1, + kFontStyleItalic = 2, + kFontStyleUnderline = 4, + kFontStyleOutline = 8, + kFontStyleShadow = 16, + kFontStyleCondensed = 32, + kFontStyleExtended = 64 +}; + +enum { + kMenuActionCommand +}; + + +struct MenuSubItem { + Common::String text; + int action; + int style; + char shortcut; + bool enabled; + Common::Rect bbox; + + MenuSubItem(const char *t, int a, int s = 0, char sh = 0, bool e = true) : text(t), action(a), style(s), shortcut(sh), enabled(e) {} +}; + +typedef Common::Array<MenuSubItem *> SubItemArray; + +struct MenuItem { + Common::String name; + SubItemArray subitems; + Common::Rect bbox; + Common::Rect subbbox; + + MenuItem(const char *n) : name(n) {} +}; + +Menu::Menu(int id, const Common::Rect &bounds, MacWindowManager *wm) + : BaseMacWindow(id, false, wm) { + _font = getMenuFont(); + + _screen.create(bounds.width(), bounds.height(), PixelFormat::createFormatCLUT8()); + + _bbox.left = 0; + _bbox.top = 0; + _bbox.right = _screen.w; + _bbox.bottom = kMenuHeight; + + _menuActivated = false; + _activeItem = -1; + _activeSubItem = -1; + + _ccallback = NULL; + _cdata = NULL; + + _tempSurface.create(_screen.w, _font->getFontHeight(), PixelFormat::createFormatCLUT8()); +} + +Menu::~Menu() { + for (uint i = 0; i < _items.size(); i++) { + for (uint j = 0; j < _items[i]->subitems.size(); j++) + delete _items[i]->subitems[j]; + delete _items[i]; + } +} + +void Menu::addStaticMenus(const MenuData *data) { + MenuItem *about = new MenuItem(_wm->hasBuiltInFonts() ? "\xa9" : "\xf0"); // (c) Symbol as the most resembling apple + _items.push_back(about); + + for (int i = 0; data[i].menunum; i++) { + const MenuData *m = &data[i]; + + if (m->menunum == kMenuHighLevel) { + MenuItem *item = new MenuItem(m->title); + _items.push_back(item); + + continue; + } + + _items[m->menunum]->subitems.push_back(new MenuSubItem(m->title, m->action, 0, m->shortcut, m->enabled)); + } +} + +int Menu::addMenuItem(const char *name) { + MenuItem *i = new MenuItem(name); + _items.push_back(i); + + return _items.size() - 1; +} + +void Menu::addMenuSubItem(int id, const char *text, int action, int style, char shortcut, bool enabled) { + _items[id]->subitems.push_back(new MenuSubItem(text, action, style, shortcut, enabled)); + + calcMenuBounds(_items[id]); +} + +void Menu::calcDimensions() { + // Calculate menu dimensions + int y = 1; + int x = 18; + + for (uint i = 0; i < _items.size(); i++) { + int w = _font->getStringWidth(_items[i]->name); + + if (_items[i]->bbox.bottom == 0) { + _items[i]->bbox.left = x - kMenuLeftMargin; + _items[i]->bbox.top = y; + _items[i]->bbox.right = x + w + kMenuSpacing - kMenuLeftMargin; + _items[i]->bbox.bottom = y + _font->getFontHeight() + (_wm->hasBuiltInFonts() ? 3 : 2); + } + + calcMenuBounds(_items[i]); + + x += w + kMenuSpacing; + } +} + +void Menu::clearSubMenu(int id) { + MenuItem *menu = _items[id]; + + for (uint j = 0; j < menu->subitems.size(); j++) + delete menu->subitems[j]; + + menu->subitems.clear(); +} + +void Menu::createSubMenuFromString(int id, const char *str) { + clearSubMenu(id); + + MenuItem *menu = _items[id]; + Common::String string(str); + + Common::String item; + + for (uint i = 0; i < string.size(); i++) { + while(i < string.size() && string[i] != ';') // Read token + item += string[i++]; + + if (item == "(-") { + menu->subitems.push_back(new MenuSubItem(NULL, 0)); + } else { + bool enabled = true; + int style = 0; + char shortcut = 0; + const char *shortPtr = strrchr(item.c_str(), '/'); + if (shortPtr != NULL) { + if (strlen(shortPtr) >= 2) { + shortcut = shortPtr[1]; + item.deleteChar(shortPtr - item.c_str()); + item.deleteChar(shortPtr - item.c_str()); + } else { + error("Unexpected shortcut: '%s', item '%s' in menu '%s'", shortPtr, item.c_str(), string.c_str()); + } + } + + while (item.size() >= 2 && item[item.size() - 2] == '<') { + char c = item.lastChar(); + if (c == 'B') { + style |= kFontStyleBold; + } else if (c == 'I') { + style |= kFontStyleItalic; + } else if (c == 'U') { + style |= kFontStyleUnderline; + } else if (c == 'O') { + style |= kFontStyleOutline; + } else if (c == 'S') { + style |= kFontStyleShadow; + } else if (c == 'C') { + style |= kFontStyleCondensed; + } else if (c == 'E') { + style |= kFontStyleExtended; + } + item.deleteLastChar(); + item.deleteLastChar(); + } + + Common::String tmpitem(item); + tmpitem.trim(); + if (tmpitem.size() > 0 && tmpitem[0] == '(') { + enabled = false; + + for (uint j = 0; j < item.size(); j++) + if (item[j] == '(') { + item.deleteChar(j); + break; + } + } + + menu->subitems.push_back(new MenuSubItem(item.c_str(), kMenuActionCommand, style, shortcut, enabled)); + } + + item.clear(); + } + + calcMenuBounds(menu); +} + +const Font *Menu::getMenuFont() { + return _wm->getFont("Chicago-12", FontManager::kBigGUIFont); +} + +const char *Menu::getAcceleratorString(MenuSubItem *item, const char *prefix) { + static char res[20]; + *res = 0; + + if (item->shortcut != 0) + sprintf(res, "%s%c%c", prefix, (_wm->hasBuiltInFonts() ? '^' : '\x11'), item->shortcut); + + return res; +} + +int Menu::calculateMenuWidth(MenuItem *menu) { + int maxWidth = 0; + for (uint i = 0; i < menu->subitems.size(); i++) { + MenuSubItem *item = menu->subitems[i]; + if (!item->text.empty()) { + Common::String text(item->text); + Common::String acceleratorText(getAcceleratorString(item, " ")); + if (!acceleratorText.empty()) { + text += acceleratorText; + } + + int width = _font->getStringWidth(text); + if (width > maxWidth) { + maxWidth = width; + } + } + } + return maxWidth; +} + +void Menu::calcMenuBounds(MenuItem *menu) { + // TODO: cache maxWidth + int maxWidth = calculateMenuWidth(menu); + int x1 = menu->bbox.left - 1; + int y1 = menu->bbox.bottom + 1; + int x2 = x1 + maxWidth + kMenuDropdownPadding * 2 - 4; + int y2 = y1 + menu->subitems.size() * kMenuDropdownItemHeight + 2; + + menu->subbbox.left = x1; + menu->subbbox.top = y1; + menu->subbbox.right = x2; + menu->subbbox.bottom = y2; +} + +static void drawPixelPlain(int x, int y, int color, void *data) { + ManagedSurface *surface = (ManagedSurface *)data; + + if (x >= 0 && x < surface->w && y >= 0 && y < surface->h) + *((byte *)surface->getBasePtr(x, y)) = (byte)color; +} + +static void drawFilledRoundRect(ManagedSurface *surface, Common::Rect &rect, int arc, int color) { + drawRoundRect(rect, arc, color, true, drawPixelPlain, surface); +} + +bool Menu::draw(ManagedSurface *g, bool forceRedraw) { + Common::Rect r(_bbox); + + if (!_contentIsDirty && !forceRedraw) + return false; + + _contentIsDirty = false; + + _screen.clear(kColorGreen); + + drawFilledRoundRect(&_screen, r, kDesktopArc, kColorWhite); + r.top = 7; + _screen.fillRect(r, kColorWhite); + r.top = kMenuHeight - 1; + r.bottom++; + _screen.fillRect(r, kColorGreen); + r.bottom--; + _screen.fillRect(r, kColorBlack); + + for (uint i = 0; i < _items.size(); i++) { + int color = kColorBlack; + MenuItem *it = _items[i]; + + if ((uint)_activeItem == i) { + Common::Rect hbox = it->bbox; + + hbox.left -= 1; + hbox.right += 3; + hbox.bottom += 1; + + _screen.fillRect(hbox, kColorBlack); + color = kColorWhite; + + if (!it->subitems.empty()) + renderSubmenu(it); + } + + _font->drawString(&_screen, it->name, it->bbox.left + kMenuLeftMargin, it->bbox.top + (_wm->hasBuiltInFonts() ? 2 : 1), it->bbox.width(), color); + } + + g->transBlitFrom(_screen, kColorGreen); + + g_system->copyRectToScreen(g->getPixels(), g->pitch, 0, 0, g->w, g->h); + + return true; +} + +void Menu::renderSubmenu(MenuItem *menu) { + Common::Rect *r = &menu->subbbox; + + if (r->width() == 0 || r->height() == 0) + return; + + _screen.fillRect(*r, kColorWhite); + _screen.frameRect(*r, kColorBlack); + _screen.vLine(r->right, r->top + 3, r->bottom + 1, kColorBlack); + _screen.vLine(r->right + 1, r->top + 3, r->bottom + 1, kColorBlack); + _screen.hLine(r->left + 3, r->bottom, r->right + 1, kColorBlack); + _screen.hLine(r->left + 3, r->bottom + 1, r->right + 1, kColorBlack); + + int x = r->left + kMenuDropdownPadding; + int y = r->top + 1; + for (uint i = 0; i < menu->subitems.size(); i++) { + Common::String text(menu->subitems[i]->text); + Common::String acceleratorText(getAcceleratorString(menu->subitems[i], "")); + int accelX = r->right - 25; + + int color = kColorBlack; + if (i == (uint)_activeSubItem && !text.empty() && menu->subitems[i]->enabled) { + color = kColorWhite; + Common::Rect trect(r->left, y - (_wm->hasBuiltInFonts() ? 1 : 0), r->right, y + _font->getFontHeight()); + + _screen.fillRect(trect, kColorBlack); + } + + if (!text.empty()) { + ManagedSurface *s = &_screen; + int tx = x, ty = y; + + if (!menu->subitems[i]->enabled) { + s = &_tempSurface; + tx = 0; + ty = 0; + accelX -= x; + + _tempSurface.clear(kColorGreen); + } + + _font->drawString(s, text, tx, ty, r->width(), color); + + if (!acceleratorText.empty()) + _font->drawString(s, acceleratorText, accelX, ty, r->width(), color); + + if (!menu->subitems[i]->enabled) { + // I am lazy to extend drawString() with plotProc as a parameter, so + // fake it here + for (int ii = 0; ii < _tempSurface.h; ii++) { + const byte *src = (const byte *)_tempSurface.getBasePtr(0, ii); + byte *dst = (byte *)_screen.getBasePtr(x, y+ii); + byte pat = _wm->getPatterns()[kPatternCheckers2 - 1][ii % 8]; + for (int j = 0; j < r->width(); j++) { + if (*src != kColorGreen && (pat & (1 << (7 - (x + j) % 8)))) + *dst = *src; + src++; + dst++; + } + } + } + } else { // Delimiter + bool flip = r->left & 2; + byte *ptr = (byte *)_screen.getBasePtr(r->left + 1, y + kMenuDropdownItemHeight / 2); + for (int xx = r->left + 1; xx <= r->right - 1; xx++, ptr++) { + *ptr = flip ? kColorBlack : kColorWhite; + flip = !flip; + } + } + + y += kMenuDropdownItemHeight; + } + + _contentIsDirty = true; + //g_system->copyRectToScreen(_screen.getBasePtr(r->left, r->top), _screen.pitch, r->left, r->top, r->width() + 2, r->height() + 2); +} + +bool Menu::processEvent(Common::Event &event) { + switch (event.type) { + case Common::EVENT_KEYDOWN: + return keyEvent(event); + case Common::EVENT_LBUTTONDOWN: + return mouseClick(event.mouse.x, event.mouse.y); + case Common::EVENT_LBUTTONUP: + return mouseRelease(event.mouse.x, event.mouse.y); + case Common::EVENT_MOUSEMOVE: + return mouseMove(event.mouse.x, event.mouse.y); + default: + return false; + } +} + +bool Menu::keyEvent(Common::Event &event) { + if (event.type != Common::EVENT_KEYDOWN) + return false; + + if (event.kbd.flags & (Common::KBD_ALT | Common::KBD_CTRL | Common::KBD_META)) { + if (event.kbd.ascii >= 0x20 && event.kbd.ascii <= 0x7f) { + return processMenuShortCut(event.kbd.flags, event.kbd.ascii); + } + } + + return false; +} + +bool Menu::mouseClick(int x, int y) { + if (_bbox.contains(x, y)) { + for (uint i = 0; i < _items.size(); i++) + if (_items[i]->bbox.contains(x, y)) { + if ((uint)_activeItem == i) + return false; + + if (_activeItem != -1) { // Restore background + Common::Rect r(_items[_activeItem]->subbbox); + r.right += 3; + r.bottom += 3; + + _wm->setFullRefresh(true); + } + + _activeItem = i; + _activeSubItem = -1; + _menuActivated = true; + + _contentIsDirty = true; + + return true; + } + } else if (_menuActivated && _items[_activeItem]->subbbox.contains(x, y)) { + MenuItem *it = _items[_activeItem]; + int numSubItem = (y - it->subbbox.top) / kMenuDropdownItemHeight; + + if (numSubItem != _activeSubItem) { + _activeSubItem = numSubItem; + + renderSubmenu(_items[_activeItem]); + _contentIsDirty = true; + } + } else if (_menuActivated && _activeItem != -1) { + _activeSubItem = -1; + + renderSubmenu(_items[_activeItem]); + _contentIsDirty = true; + } + + return false; +} + +bool Menu::mouseMove(int x, int y) { + if (_menuActivated) + if (mouseClick(x, y)) + return true; + + return false; +} + +bool Menu::mouseRelease(int x, int y) { + if (_menuActivated) { + _menuActivated = false; + + if (_activeItem != -1 && _activeSubItem != -1 && _items[_activeItem]->subitems[_activeSubItem]->enabled) + (*_ccallback)(_items[_activeItem]->subitems[_activeSubItem]->action, + _items[_activeItem]->subitems[_activeSubItem]->text, _cdata); + + _activeItem = -1; + _activeSubItem = -1; + + _wm->setFullRefresh(true); + + return true; + } + + return false; +} + +bool Menu::processMenuShortCut(byte flags, uint16 ascii) { + ascii = tolower(ascii); + + if (flags & (Common::KBD_CTRL | Common::KBD_META)) { + for (uint i = 0; i < _items.size(); i++) + for (uint j = 0; j < _items[i]->subitems.size(); j++) + if (_items[i]->subitems[j]->enabled && tolower(_items[i]->subitems[j]->shortcut) == ascii) { + (*_ccallback)(_items[i]->subitems[j]->action, _items[i]->subitems[j]->text, _cdata); + return true; + } + } + + return false; +} + +void Menu::enableCommand(int menunum, int action, bool state) { + for (uint i = 0; i < _items[menunum]->subitems.size(); i++) + if (_items[menunum]->subitems[i]->action == action) + _items[menunum]->subitems[i]->enabled = state; + + _contentIsDirty = true; +} + +void Menu::disableAllMenus() { + for (uint i = 1; i < _items.size(); i++) // Leave About menu on + for (uint j = 0; j < _items[i]->subitems.size(); j++) + _items[i]->subitems[j]->enabled = false; + + _contentIsDirty = true; +} + +} // End of namespace Wage diff --git a/graphics/macgui/macmenu.h b/graphics/macgui/macmenu.h new file mode 100644 index 0000000000..7114066ae6 --- /dev/null +++ b/graphics/macgui/macmenu.h @@ -0,0 +1,123 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * MIT License: + * + * Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef GRAPHICS_MACGUI_MACMENU_H +#define GRAPHICS_MACGUI_MACMENU_H + +namespace Graphics { + +struct MenuItem; +struct MenuSubItem; + +struct MenuData { + int menunum; + const char *title; + int action; + byte shortcut; + bool enabled; +}; + +class Menu : public BaseMacWindow { +public: + Menu(int id, const Common::Rect &bounds, MacWindowManager *wm); + ~Menu(); + + void setCommandsCallback(void (*callback)(int, Common::String &, void *), void *data) { _ccallback = callback; _cdata = data; } + + void addStaticMenus(const MenuData *data); + void calcDimensions(); + + int addMenuItem(const char *name); + void addMenuSubItem(int id, const char *text, int action, int style = 0, char shortcut = 0, bool enabled = true); + void createSubMenuFromString(int id, const char *string); + void clearSubMenu(int id); + + bool draw(ManagedSurface *g, bool forceRedraw = false); + bool processEvent(Common::Event &event); + + void enableCommand(int menunum, int action, bool state); + void disableAllMenus(); + + void setActive(bool active) { _menuActivated = active; } + bool hasAllFocus() { return _menuActivated; } + + Common::Rect _bbox; + +private: + ManagedSurface _screen; + ManagedSurface _tempSurface; + +private: + const Font *getMenuFont(); + const char *getAcceleratorString(MenuSubItem *item, const char *prefix); + int calculateMenuWidth(MenuItem *menu); + void calcMenuBounds(MenuItem *menu); + void renderSubmenu(MenuItem *menu); + + bool keyEvent(Common::Event &event); + bool mouseClick(int x, int y); + bool mouseRelease(int x, int y); + bool mouseMove(int x, int y); + + bool processMenuShortCut(byte flags, uint16 ascii); + + Common::Array<MenuItem *> _items; + + const Font *_font; + + bool _menuActivated; + + int _activeItem; + int _activeSubItem; + + void (*_ccallback)(int action, Common::String &text, void *data); + void *_cdata; +}; + +} // End of namespace Graphics + +#endif diff --git a/graphics/macgui/macwindow.cpp b/graphics/macgui/macwindow.cpp new file mode 100644 index 0000000000..dbb600ba82 --- /dev/null +++ b/graphics/macgui/macwindow.cpp @@ -0,0 +1,496 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * MIT License: + * + * Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "graphics/font.h" +#include "graphics/primitives.h" +#include "common/events.h" +#include "graphics/macgui/macwindowmanager.h" +#include "graphics/macgui/macwindow.h" +#include "image/bmp.h" + +namespace Graphics { + +BaseMacWindow::BaseMacWindow(int id, bool editable, MacWindowManager *wm) : + _id(id), _editable(editable), _wm(wm) { + _callback = 0; + _dataPtr = 0; + + _contentIsDirty = true; + + _type = kWindowUnknown; +} + +MacWindow::MacWindow(int id, bool scrollable, bool resizable, bool editable, MacWindowManager *wm) : + BaseMacWindow(id, editable, wm), _scrollable(scrollable), _resizable(resizable) { + _active = false; + _borderIsDirty = true; + + _highlightedPart = kBorderNone; + + _scrollPos = _scrollSize = 0.0; + + _beingDragged = false; + _beingResized = false; + + _draggedX = _draggedY = 0; + + _type = kWindowWindow; + + _closeable = false; + + _borderWidth = kBorderWidth; +} + +MacWindow::~MacWindow() { +} + +const Font *MacWindow::getTitleFont() { + return _wm->getFont("Chicago-12", FontManager::kBigGUIFont); +} + +void MacWindow::setActive(bool active) { + if (active == _active) + return; + + _active = active; + _borderIsDirty = true; +} + +bool MacWindow::isActive() { return _active; } + +void MacWindow::resize(int w, int h) { + if (_surface.w == w && _surface.h == h) + return; + + _surface.free(); + _surface.create(w, h, PixelFormat::createFormatCLUT8()); + _borderSurface.free(); + _borderSurface.create(w, h, PixelFormat::createFormatCLUT8()); + _composeSurface.free(); + _composeSurface.create(w, h, PixelFormat::createFormatCLUT8()); + + _dims.setWidth(w); + _dims.setHeight(h); + + updateInnerDims(); + + _contentIsDirty = true; + _borderIsDirty = true; +} + +void MacWindow::move(int x, int y) { + if (_dims.left == x && _dims.top == y) + return; + + _dims.moveTo(x, y); + updateInnerDims(); + + _contentIsDirty = true; +} + +void MacWindow::setDimensions(const Common::Rect &r) { + resize(r.width(), r.height()); + _dims.moveTo(r.left, r.top); + updateInnerDims(); + + _contentIsDirty = true; +} + +bool MacWindow::draw(ManagedSurface *g, bool forceRedraw) { + if (!_borderIsDirty && !_contentIsDirty && !forceRedraw) + return false; + + if (_borderIsDirty || forceRedraw) + drawBorder(); + + _contentIsDirty = false; + + // Compose + _composeSurface.blitFrom(_surface, Common::Rect(0, 0, _surface.w - 2, _surface.h - 2), Common::Point(2, 2)); + _composeSurface.transBlitFrom(_borderSurface, kColorGreen); + + g->transBlitFrom(_composeSurface, _composeSurface.getBounds(), Common::Point(_dims.left - 2, _dims.top - 2), kColorGreen2); + + return true; +} + + +#define ARROW_W 12 +#define ARROW_H 6 +const int arrowPixels[ARROW_H][ARROW_W] = { + {0,0,0,0,0,1,1,0,0,0,0,0}, + {0,0,0,0,1,1,1,1,0,0,0,0}, + {0,0,0,1,1,1,1,1,1,0,0,0}, + {0,0,1,1,1,1,1,1,1,1,0,0}, + {0,1,1,1,1,1,1,1,1,1,1,0}, + {1,1,1,1,1,1,1,1,1,1,1,1}}; + +static void drawPixelInverted(int x, int y, int color, void *data) { + ManagedSurface *surface = (ManagedSurface *)data; + + if (x >= 0 && x < surface->w && y >= 0 && y < surface->h) { + byte *p = (byte *)surface->getBasePtr(x, y); + + *p = *p == kColorWhite ? kColorBlack : kColorWhite; + } +} + +void MacWindow::updateInnerDims() { + if (_macBorder.hasBorder(_active) && _macBorder.hasOffsets()) { + _innerDims = Common::Rect( + _dims.left + _macBorder.getOffset(kBorderOffsetLeft), + _dims.top + _macBorder.getOffset(kBorderOffsetTop), + _dims.right - _macBorder.getOffset(kBorderOffsetRight), + _dims.bottom - _macBorder.getOffset(kBorderOffsetBottom)); + } else { + _innerDims = _dims; + _innerDims.grow(-kBorderWidth); + } +} + +void MacWindow::drawBorder() { + _borderIsDirty = false; + + ManagedSurface *g = &_borderSurface; + + if (_macBorder.hasBorder(_active)) { + drawBorderFromSurface(g); + } else { + drawSimpleBorder(g); + } +} + +void MacWindow::prepareBorderSurface(ManagedSurface *g) { + // We draw rect with outer kColorGreen2 and inner kColorGreen, so on 2 passes we cut out + // scene by external shape of the border + int sz = kBorderWidth / 2; + int width = g->w; + int height = g->h; + g->clear(kColorGreen2); + g->fillRect(Common::Rect(sz, sz, width - sz, height - sz), kColorGreen); +} + +void MacWindow::drawBorderFromSurface(ManagedSurface *g) { + g->clear(kColorGreen2); + Common::Rect inside = _innerDims; + inside.moveTo(_macBorder.getOffset(kBorderOffsetLeft), _macBorder.getOffset(kBorderOffsetTop)); + g->fillRect(inside, kColorGreen); + + _macBorder.blitBorderInto(_borderSurface, _active); +} + +void MacWindow::drawSimpleBorder(ManagedSurface *g) { + + bool active = _active, scrollable = _scrollable, closeable = _active, drawTitle = !_title.empty(); + const int size = kBorderWidth; + int x = 0; + int y = 0; + int width = _borderSurface.w; + int height = _borderSurface.h; + + prepareBorderSurface(g); + + drawBox(g, x, y, size, size); + drawBox(g, x + width - size - 1, y, size, size); + drawBox(g, x + width - size - 1, y + height - size - 1, size, size); + drawBox(g, x, y + height - size - 1, size, size); + drawBox(g, x + size, y + 2, width - 2 * size - 1, size - 4); + drawBox(g, x + size, y + height - size + 1, width - 2 * size - 1, size - 4); + drawBox(g, x + 2, y + size, size - 4, height - 2 * size - 1); + drawBox(g, x + width - size + 1, y + size, size - 4, height - 2 * size - 1); + + if (active) { + fillRect(g, x + size, y + 5, width - 2 * size - 1, 8, kColorBlack); + fillRect(g, x + size, y + height - 13, width - 2 * size - 1, 8, kColorBlack); + fillRect(g, x + 5, y + size, 8, height - 2 * size - 1, kColorBlack); + if (!scrollable) { + fillRect(g, x + width - 13, y + size, 8, height - 2 * size - 1, kColorBlack); + } else { + int x1 = x + width - 15; + int y1 = y + size + 1; + + for (int yy = 0; yy < ARROW_H; yy++) { + for (int xx = 0; xx < ARROW_W; xx++) + g->hLine(x1 + xx, y1 + yy, x1 + xx, (arrowPixels[yy][xx] != 0 ? kColorBlack : kColorWhite)); + } + + fillRect(g, x + width - 13, y + size + ARROW_H, 8, height - 2 * size - 1 - ARROW_H * 2, kColorBlack); + + y1 += height - 2 * size - ARROW_H - 2; + for (int yy = 0; yy < ARROW_H; yy++) { + for (int xx = 0; xx < ARROW_W; xx++) + g->hLine(x1 + xx, y1 + yy, x1 + xx, (arrowPixels[ARROW_H - yy - 1][xx] != 0 ? kColorBlack : kColorWhite)); + } + + if (_highlightedPart == kBorderScrollUp || _highlightedPart == kBorderScrollDown) { + int rx1 = x + width - kBorderWidth + 2; + int ry1 = y + size + _dims.height() * _scrollPos; + int rx2 = rx1 + size - 4; + int ry2 = ry1 + _dims.height() * _scrollSize; + Common::Rect rr(rx1, ry1, rx2, ry2); + + Graphics::drawFilledRect(rr, kColorBlack, drawPixelInverted, g); + } + } + if (closeable) { + if (_highlightedPart == kBorderCloseButton) { + fillRect(g, x + 6, y + 6, 6, 6, kColorBlack); + } else { + drawBox(g, x + 5, y + 5, 7, 7); + } + } + } + + if (drawTitle) { + const Graphics::Font *font = getTitleFont(); + int yOff = _wm->hasBuiltInFonts() ? 3 : 1; + + int w = font->getStringWidth(_title) + 10; + int maxWidth = width - size * 2 - 7; + if (w > maxWidth) + w = maxWidth; + drawBox(g, x + (width - w) / 2, y, w, size); + font->drawString(g, _title, x + (width - w) / 2 + 5, y + yOff, w, kColorBlack); + } +} + +void MacWindow::setHighlight(WindowClick highlightedPart) { + if (_highlightedPart == highlightedPart) + return; + + _highlightedPart = highlightedPart; + _borderIsDirty = true; +} + +void MacWindow::setScroll(float scrollPos, float scrollSize) { + if (_scrollPos == scrollPos && _scrollSize == scrollSize) + return; + + _scrollPos = scrollPos; + _scrollSize = scrollSize; + _borderIsDirty = true; +} + +void MacWindow::loadBorder(Common::SeekableReadStream &file, bool active, int lo, int ro, int to, int bo) { + Image::BitmapDecoder bmpDecoder; + Graphics::Surface source; + Graphics::TransparentSurface *surface = new Graphics::TransparentSurface(); + + bmpDecoder.loadStream(file); + source = *(bmpDecoder.getSurface()); + + source.convertToInPlace(surface->getSupportedPixelFormat(), bmpDecoder.getPalette()); + surface->create(source.w, source.h, source.format); + surface->copyFrom(source); + surface->applyColorKey(255, 0, 255, false); + + if (active) + _macBorder.addActiveBorder(*surface); + else + _macBorder.addInactiveBorder(*surface); + + if (!_macBorder.hasOffsets()) + _macBorder.setOffsets(lo, ro, to, bo); + + updateInnerDims(); +} + +void MacWindow::setCloseable(bool closeable) { + _closeable = closeable; +} + +void MacWindow::drawBox(ManagedSurface *g, int x, int y, int w, int h) { + Common::Rect r(x, y, x + w + 1, y + h + 1); + + g->fillRect(r, kColorWhite); + g->frameRect(r, kColorBlack); +} + +void MacWindow::fillRect(ManagedSurface *g, int x, int y, int w, int h, int color) { + Common::Rect r(x, y, x + w, y + h); + + g->fillRect(r, color); +} + +WindowClick MacWindow::isInBorder(int x, int y) { + if (_innerDims.contains(x, y)) + return kBorderInner; + + if (isInCloseButton(x, y)) + return kBorderCloseButton; + + if (_resizable) + if (isInResizeButton(x, y)) + return kBorderResizeButton; + + if (_scrollable) + return isInScroll(x, y); + + return kBorderBorder; +} + +bool MacWindow::isInCloseButton(int x, int y) { + int bLeft = kBorderWidth; + int bTop = kBorderWidth; + if (_macBorder.hasOffsets()) { + bLeft = _macBorder.getOffset(kBorderOffsetLeft); + bTop = _macBorder.getOffset(kBorderOffsetTop); + } + return (x >= _innerDims.left - bLeft && x < _innerDims.left && y >= _innerDims.top - bTop && y < _innerDims.top); +} + +bool MacWindow::isInResizeButton(int x, int y) { + int bRight = kBorderWidth; + int bBottom = kBorderWidth; + if (_macBorder.hasOffsets()) { + bRight = _macBorder.getOffset(kBorderOffsetRight); + bBottom = _macBorder.getOffset(kBorderOffsetBottom); + } + return (x >= _innerDims.right && x < _innerDims.right + bRight && y >= _innerDims.bottom && y < _innerDims.bottom + bBottom); +} + +WindowClick MacWindow::isInScroll(int x, int y) { + int bTop = kBorderWidth; + int bRight = kBorderWidth; + int bBottom = kBorderWidth; + if (_macBorder.hasOffsets()) { + bTop = _macBorder.getOffset(kBorderOffsetTop); + bRight = _macBorder.getOffset(kBorderOffsetRight); + bBottom = _macBorder.getOffset(kBorderOffsetBottom); + } + + if (x >= _innerDims.right && x < _innerDims.right + bRight) { + if (y < _innerDims.top - bTop) + return kBorderBorder; + + if (y >= _innerDims.bottom + bBottom) + return kBorderBorder; + + if (y >= _innerDims.top + _innerDims.height() / 2) + return kBorderScrollDown; + + return kBorderScrollUp; + } + + if (y >= _innerDims.bottom && y < _innerDims.bottom + bBottom) { + if (x < _innerDims.left - bTop) + return kBorderBorder; + + if (x >= _innerDims.right + bRight) + return kBorderBorder; + + if (x >= _innerDims.left + _innerDims.width() / 2) + return kBorderScrollRight; + + return kBorderScrollLeft; + } + + return kBorderBorder; +} + +bool MacWindow::processEvent(Common::Event &event) { + WindowClick click = isInBorder(event.mouse.x, event.mouse.y); + + switch (event.type) { + case Common::EVENT_MOUSEMOVE: + if (_beingDragged) { + _dims.translate(event.mouse.x - _draggedX, event.mouse.y - _draggedY); + updateInnerDims(); + + _draggedX = event.mouse.x; + _draggedY = event.mouse.y; + + _wm->setFullRefresh(true); + } + + if (_beingResized) { + resize(MAX(_borderWidth * 4, _dims.width() + event.mouse.x - _draggedX), + MAX(_borderWidth * 4, _dims.height() + event.mouse.y - _draggedY)); + + _draggedX = event.mouse.x; + _draggedY = event.mouse.y; + + _wm->setFullRefresh(true); + (*_callback)(click, event, _dataPtr); + } + break; + case Common::EVENT_LBUTTONDOWN: + setHighlight(click); + + if (click == kBorderBorder) { + _beingDragged = true; + + _draggedX = event.mouse.x; + _draggedY = event.mouse.y; + } + + if (click == kBorderResizeButton) { + _beingResized = true; + + _draggedX = event.mouse.x; + _draggedY = event.mouse.y; + } + + if (click == kBorderCloseButton && _closeable) { + _wm->removeWindow(this); + } + + break; + case Common::EVENT_LBUTTONUP: + _beingDragged = false; + _beingResized = false; + + setHighlight(kBorderNone); + break; + default: + return false; + } + + return (*_callback)(click, event, _dataPtr); +} + +} // End of namespace Wage diff --git a/graphics/macgui/macwindow.h b/graphics/macgui/macwindow.h new file mode 100644 index 0000000000..c40c1a4a38 --- /dev/null +++ b/graphics/macgui/macwindow.h @@ -0,0 +1,350 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * MIT License: + * + * Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef GRAPHICS_MACGUI_MACWINDOW_H +#define GRAPHICS_MACGUI_MACWINDOW_H + +#include "graphics/managed_surface.h" +#include "graphics/transparent_surface.h" +#include "graphics/nine_patch.h" +#include "graphics/palette.h" + +#include "graphics/macgui/macwindowborder.h" + +namespace Graphics { + +class MacWindowManager; +class MacWindowBorder; + +namespace MacWindowConstants { +enum WindowType { + kWindowUnknown, + kWindowWindow, + kWindowMenu +}; + +enum { + kBorderWidth = 17 +}; + +enum WindowClick { + kBorderNone = 0, + kBorderScrollUp, + kBorderScrollDown, + kBorderScrollLeft, + kBorderScrollRight, + kBorderCloseButton, + kBorderInner, + kBorderBorder, + kBorderResizeButton +}; +} +using namespace MacWindowConstants; + +/** + * Abstract class that defines common functionality for all window classes. + * It supports event callbacks and drawing. + */ +class BaseMacWindow { +public: + /** + * Base constructor. + * @param id ID of the window. + * @param editable True if the window is editable. + * @param wm Pointer to the MacWindowManager that owns the window. + */ + BaseMacWindow(int id, bool editable, MacWindowManager *wm); + virtual ~BaseMacWindow() {} + + /** + * Accessor method for the complete dimensions of the window. + * @return Dimensions of the window (including border) relative to the WM's screen. + */ + const Common::Rect &getDimensions() { return _dims; } + + /** + * Accessor method to the id of the window. + * @return The id set in the constructor. + */ + int getId() { return _id; } + + /** + * Accessor method to the type of window. + * Each subclass must indicate it's type. + * @return The type of the window. + */ + WindowType getType() { return _type; } + + /** + * Accessor method to check whether the window is editable (e.g. for resizing). + * @return True if the window is editable as indicated in the constructor. + */ + bool isEditable() { return _editable; } + + /** + * Method to access the entire surface of the window (e.g. to draw an image). + * @return A pointer to the entire surface of the window. + */ + ManagedSurface *getSurface() { return &_surface; } + + /** + * Abstract method for indicating whether the window is active or inactive. + * Used by the WM to handle focus on windows, etc. + * @param active Desired state of the window. + */ + virtual void setActive(bool active) = 0; + + /** + * Method for marking the window for redraw. + * @param dirty True if the window needs to be redrawn. + */ + void setDirty(bool dirty) { _contentIsDirty = dirty; } + + /** + * Method called to draw the window into the target surface. + * This method is most often called by the WM, and relies on + * the window being marked as dirty unless otherwise specified. + * @param g Surface on which to draw the window. + * @param forceRedraw It's behavior depends on the subclass. + */ + virtual bool draw(ManagedSurface *g, bool forceRedraw = false) = 0; + + /** + * Method called by the WM when there is an event concerning the window. + * Note that depending on the subclass of the window, it might not be called + * if the window is not active. + * @param event Event to be processed. + * @return true If the event was successfully consumed and processed. + */ + virtual bool processEvent(Common::Event &event) = 0; + + virtual bool hasAllFocus() = 0; + + /** + * Set the callback that will be used when an event needs to be processed. + * @param callback A function pointer to a function that accepts: + * - A WindowClick, the pert of the window that was clicked. + * - The event to be processed. + * - Any additional required data (e.g. the engine's GUI). + */ + void setCallback(bool (*callback)(WindowClick, Common::Event &, void *), void *data) { _callback = callback; _dataPtr = data; } + +protected: + int _id; + WindowType _type; + + bool _editable; + + ManagedSurface _surface; + bool _contentIsDirty; + + Common::Rect _dims; + + bool (*_callback)(WindowClick, Common::Event &, void *); + void *_dataPtr; + + MacWindowManager *_wm; +}; + +/** + * An implementation of an ordinary window in the Mac interface. + * It supports custom resizing, scrolling, borders, etc. + */ +class MacWindow : public BaseMacWindow { +public: + /** + * Construct a simple window, with the default settings. + * Note that the scroll must be implemented in the event handling, + * even if the scrollable flag is set to true. + * @param id See BaseMacWindow. + * @param scrollable True if the window can be scrolled. + * @param resizable True if the window can be resized. + * @param editable See BaseMacWindow. + * @param wm See BaseMacWindow. + */ + MacWindow(int id, bool scrollable, bool resizable, bool editable, MacWindowManager *wm); + virtual ~MacWindow(); + + /** + * Change the window's location to fixed coordinates (not delta). + * @param x New left position of the window relative to the WM's screen. + * @param y New top position of the window relative to the WM's screen. + */ + void move(int x, int y); + + /* + * Change the width and the height of the window. + * @param w New width of the window. + * @param h New height of the window. + */ + void resize(int w, int h); + + /** + * Change the dimensions of the window ([0, 0, 0, 0] by default). + * Note that this can be used to update both the position and the size + * of the window, although move() and resize() might be more comfortable. + * @param r The desired dimensions of the window. + */ + void setDimensions(const Common::Rect &r); + + /** + * Accessor to retrieve the dimensions of the inner surface of the window + * (i.e. without taking borders into account). + * Note that the returned dimensions' position is relative to the WM's + * screen, just like in getDimensions(). + * @return The inner dimensions of the window. + */ + const Common::Rect &getInnerDimensions() { return _innerDims; } + + /** + * Similar to that described in BaseMacWindow. + * @param g See BaseMacWindow. + * @param forceRedraw If true, the borders are guarranteed to redraw. + */ + bool draw(ManagedSurface *g, bool forceRedraw = false); + + /** + * Mutator to change the active state of the window. + * Most often called from the WM. + * @param active Target state. + */ + void setActive(bool active); + /** + * Accessor to determine whether a window is active. + * @return True if the window is active. + */ + bool isActive(); + + /** + * Mutator to change the title of the window. + * @param title Target title of the window. + */ + void setTitle(Common::String &title) { _title = title; } + /** + * Highlight the target part of the window. + * Used for the default borders. + * @param highlightedPart Part to be highlighted. + */ + void setHighlight(WindowClick highlightedPart); + /** + * Set the scroll poisition. + * @param scrollPos Target scroll position. + * @param scrollSize Size of the scrolling bar. + */ + void setScroll(float scrollPos, float scrollSize); + /** + * See BaseMacWindow. + */ + bool processEvent(Common::Event &event); + bool hasAllFocus() { return _beingDragged || _beingResized; } + + /** + * Set arbitrary border from a BMP data stream, with custom border offsets. + * Note that the BMP has to be 9patch compliant. For examples, go to: + * https://github.com/blorente/MacVenture-Extract-Guide/tree/master/borders + * @param file The BMP data stream with the desired border. + * @param active Whether the border corresponds with the active state of the window. + * @param lo Width of the left side of the border, in pixels. + * @param ro Width of the right side of the border, in pixels. + * @param to Width of the top side of the border, in pixels. + * @param bo Width of the bottom side of the border, in pixels. + */ + void loadBorder(Common::SeekableReadStream &file, bool active, int lo, int ro, int to, int bo); + //void setBorder(TransparentSurface &border, bool active); + + /** + * Indicate whether the window can be closed (false by default). + * @param closeable True if the window can be closed. + */ + void setCloseable(bool closeable); + +private: + void drawBorder(); + void prepareBorderSurface(ManagedSurface *g); + void drawSimpleBorder(ManagedSurface *g); + void drawBorderFromSurface(ManagedSurface *g); + void drawBox(ManagedSurface *g, int x, int y, int w, int h); + void fillRect(ManagedSurface *g, int x, int y, int w, int h, int color); + const Font *getTitleFont(); + void updateInnerDims(); + WindowClick isInBorder(int x, int y); + + bool isInCloseButton(int x, int y); + bool isInResizeButton(int x, int y); + WindowClick isInScroll(int x, int y); + +private: + ManagedSurface _borderSurface; + ManagedSurface _composeSurface; + + MacWindowBorder _macBorder; + + bool _scrollable; + bool _resizable; + bool _active; + bool _borderIsDirty; + + bool _closeable; + + int _borderWidth; + + bool _beingDragged, _beingResized; + int _draggedX, _draggedY; + + WindowClick _highlightedPart; + float _scrollPos, _scrollSize; + + Common::Rect _innerDims; + + Common::String _title; +}; + + + +} // End of namespace Graphics + +#endif diff --git a/graphics/macgui/macwindowborder.cpp b/graphics/macgui/macwindowborder.cpp new file mode 100644 index 0000000000..b77fa35603 --- /dev/null +++ b/graphics/macgui/macwindowborder.cpp @@ -0,0 +1,117 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. + +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. + +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +* MIT License: +* +* Copyright (c) 2016 Borja Lorente +* +* Permission is hereby granted, free of charge, to any person +* obtaining a copy of this software and associated documentation +* files (the "Software"), to deal in the Software without +* restriction, including without limitation the rights to use, +* copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the +* Software is furnished to do so, subject to the following +* conditions: +* +* The above copyright notice and this permission notice shall be +* included in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +* OTHER DEALINGS IN THE SOFTWARE. +* +*/ + +#include "common/system.h" + +#include "graphics/macgui/macwindowborder.h" +#include "graphics/macgui/macwindowmanager.h" + +namespace Graphics { + +using namespace Graphics::MacGUIConstants; + +MacWindowBorder::MacWindowBorder() : _activeInitialized(false), _inactiveInitialized(false) { + _activeBorder = nullptr; + _inactiveBorder = nullptr; + _hasOffsets = false; +} + +MacWindowBorder::~MacWindowBorder() { + if (_activeBorder) + delete _activeBorder; + if (_inactiveBorder) + delete _inactiveBorder; +} + +bool MacWindowBorder::hasBorder(bool active) { + return active ? _activeInitialized : _inactiveInitialized; +} + +void MacWindowBorder::addActiveBorder(TransparentSurface &source) { + assert(!_activeBorder); + _activeBorder = new NinePatchBitmap(&source, false); + _activeInitialized = true; +} + +void MacWindowBorder::addInactiveBorder(TransparentSurface &source) { + assert(!_inactiveBorder); + _inactiveBorder = new NinePatchBitmap(&source, false); + _inactiveInitialized = true; +} + +bool MacWindowBorder::hasOffsets() { + return _hasOffsets; +} + +void MacWindowBorder::setOffsets(int left, int right, int top, int bottom) { + _borderOffsets[0] = left; + _borderOffsets[1] = right; + _borderOffsets[2] = top; + _borderOffsets[3] = bottom; + _hasOffsets = true; +} + +int MacWindowBorder::getOffset(MacBorderOffset offset) { + return _borderOffsets[offset]; +} + +void MacWindowBorder::blitBorderInto(ManagedSurface &destination, bool active) { + + TransparentSurface srf; + NinePatchBitmap *src = active ? _activeBorder : _inactiveBorder; + + srf.create(destination.w, destination.h, destination.format); + srf.fillRect(Common::Rect(0, 0, srf.w, srf.h), kColorGreen2); + + byte palette[kColorCount]; + g_system->getPaletteManager()->grabPalette(palette, 0, kColorCount); + + src->blit(srf, 0, 0, srf.w, srf.h, palette, kColorCount); + destination.transBlitFrom(srf, kColorGreen2); +} + +} // End of namespace Graphics diff --git a/graphics/macgui/macwindowborder.h b/graphics/macgui/macwindowborder.h new file mode 100644 index 0000000000..54938e5143 --- /dev/null +++ b/graphics/macgui/macwindowborder.h @@ -0,0 +1,149 @@ +/* ScummVM - Graphic Adventure Engine +* +* ScummVM is the legal property of its developers, whose names +* are too numerous to list here. Please refer to the COPYRIGHT +* file distributed with this source distribution. +* +* This program is free software; you can redistribute it and/or +* modify it under the terms of the GNU General Public License +* as published by the Free Software Foundation; either version 2 +* of the License, or (at your option) any later version. + +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. + +* You should have received a copy of the GNU General Public License +* along with this program; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +* +* MIT License: +* +* Copyright (c) 2016 Borja Lorente +* +* Permission is hereby granted, free of charge, to any person +* obtaining a copy of this software and associated documentation +* files (the "Software"), to deal in the Software without +* restriction, including without limitation the rights to use, +* copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the +* Software is furnished to do so, subject to the following +* conditions: +* +* The above copyright notice and this permission notice shall be +* included in all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +* OTHER DEALINGS IN THE SOFTWARE. +* +*/ + +#ifndef GRAPHICS_MACGUI_MACWINDOWBORDER_H +#define GRAPHICS_MACGUI_MACWINDOWBORDER_H + +#include "common/str.h" +#include "common/list.h" + +#include "graphics/nine_patch.h" +#include "graphics/managed_surface.h" +#include "graphics/transparent_surface.h" + +namespace Graphics { + +enum MacBorderOffset { + kBorderOffsetLeft = 0, + kBorderOffsetRight = 1, + kBorderOffsetTop = 2, + kBorderOffsetBottom = 3 +}; + +/** + * A representation of a custom border, which allows for arbitrary border offsets + * and nine-patch resizable displays for both active and inactive states. + * However, the border offsets are the same for both active and inactive states. + */ +class MacWindowBorder { +public: + MacWindowBorder(); + ~MacWindowBorder(); + + /** + * Accessor to check whether or not a border is loaded. + * @param active State that we want to check. If true it checks for active border, if false it checks for inactive. + * @return True if the checked state has a border loaded, false otherwise. + */ + bool hasBorder(bool active); + + /** + * Add the given surface as the display of the border in the active state. + * Will fail if there is already an active border. + * @param The surface that will be displayed. + */ + void addActiveBorder(TransparentSurface &source); + + /** + * Add the given surface as the display of the border in the inactive state. + * Will fail if there is already an inactive border. + * @param The surface that will be displayed. + */ + void addInactiveBorder(TransparentSurface &source); + + /** + * Accessor function for the custom offsets. + * @return True if custom offsets have been indicated (setOffsets has been called previously). + */ + bool hasOffsets(); + + /** + * Mutator method to indicate the custom border offsets. + * These should be set to the desired thickness of each side of the border. + * e.g. For a border that is 10 pixels wide and 5 pixels tall, the call should be: + * setOffsets(10, 10, 5, 5) + * Note that this function does not check whether those borders form + * a valid rect when combined with the window dimensions. + * @param left Thickness (in pixels) of the left side of the border. + * @param right Thickness (in pixels) of the right side of the border. + * @param top Thickness (in pixels) of the top side of the border. + * @param bottom Thickness (in pixels) of the bottom side of the border. + */ + void setOffsets(int left, int right, int top, int bottom); + + /** + * Accessor method to retrieve a given border. + * Note that it does not check for validity, and thus if setOffsets + * was not called before it might return garbage. + * @param offset The identifier of the offset wanted. + * @return The desired offset in pixels. + */ + int getOffset(MacBorderOffset offset); + + /** + * Blit the desired border (active or inactive) into a destination surface. + * It automatically resizes the border to fit the given surface. + * @param destination The surface we want to blit into. + * @param active True if we want to blit the active border, false otherwise. + */ + void blitBorderInto(ManagedSurface &destination, bool active); + +private: + + NinePatchBitmap *_activeBorder; + NinePatchBitmap *_inactiveBorder; + + bool _activeInitialized; + bool _inactiveInitialized; + + bool _hasOffsets; + int _borderOffsets[4]; + +}; + +} // End of namespace Graphics +#endif diff --git a/graphics/macgui/macwindowmanager.cpp b/graphics/macgui/macwindowmanager.cpp new file mode 100644 index 0000000000..d19e40781f --- /dev/null +++ b/graphics/macgui/macwindowmanager.cpp @@ -0,0 +1,478 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * MIT License: + * + * Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#include "common/array.h" +#include "common/events.h" +#include "common/list.h" +#include "common/unzip.h" +#include "common/system.h" +#include "common/stream.h" + +#include "graphics/cursorman.h" +#include "graphics/fonts/bdf.h" +#include "graphics/managed_surface.h" +#include "graphics/palette.h" +#include "graphics/primitives.h" +#include "graphics/macgui/macwindowmanager.h" +#include "graphics/macgui/macwindow.h" +#include "graphics/macgui/macmenu.h" + +namespace Graphics { + +static const byte palette[] = { + 0, 0, 0, // Black + 0x80, 0x80, 0x80, // Gray + 0xff, 0xff, 0xff, // White + 0x00, 0xff, 0x00, // Green + 0x00, 0xcf, 0x00 // Green2 +}; + +static byte fillPatterns[][8] = { { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, // kPatternSolid + { 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55 }, // kPatternStripes + { 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55 }, // kPatternCheckers + { 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa } // kPatternCheckers2 +}; + +static const byte macCursorArrow[] = { + 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 2, 0, 2, 3, 3, 3, 3, 3, 3, 3, 3, + 2, 0, 0, 2, 3, 3, 3, 3, 3, 3, 3, + 2, 0, 0, 0, 2, 3, 3, 3, 3, 3, 3, + 2, 0, 0, 0, 0, 2, 3, 3, 3, 3, 3, + 2, 0, 0, 0, 0, 0, 2, 3, 3, 3, 3, + 2, 0, 0, 0, 0, 0, 0, 2, 3, 3, 3, + 2, 0, 0, 0, 0, 0, 0, 0, 2, 3, 3, + 2, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, + 2, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2, + 2, 0, 0, 2, 0, 0, 2, 3, 3, 3, 3, + 2, 0, 2, 3, 2, 0, 0, 2, 3, 3, 3, + 2, 2, 3, 3, 2, 0, 0, 2, 3, 3, 3, + 2, 3, 3, 3, 3, 2, 0, 0, 2, 3, 3, + 3, 3, 3, 3, 3, 2, 0, 0, 2, 3, 3, + 3, 3, 3, 3, 3, 3, 2, 2, 2, 3, 3 +}; + +static const byte macCursorBeam[] = { + 0, 0, 3, 3, 3, 0, 0, 3, 3, 3, 3, + 3, 3, 0, 3, 0, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 0, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 0, 3, 0, 3, 3, 3, 3, 3, 3, + 0, 0, 3, 3, 3, 0, 0, 3, 3, 3, 3, +}; + +MacWindowManager::MacWindowManager() { + _screen = 0; + _lastId = 0; + _activeWindow = -1; + + _menu = 0; + + _fullRefresh = true; + + _builtInFonts = true; + + for (int i = 0; i < ARRAYSIZE(fillPatterns); i++) + _patterns.push_back(fillPatterns[i]); + + loadFonts(); + + g_system->getPaletteManager()->setPalette(palette, 0, ARRAYSIZE(palette) / 3); + + CursorMan.replaceCursorPalette(palette, 0, ARRAYSIZE(palette) / 3); + CursorMan.replaceCursor(macCursorArrow, 11, 16, 1, 1, 3); + _cursorIsArrow = true; + CursorMan.showMouse(true); +} + +MacWindowManager::~MacWindowManager() { + for (int i = 0; i < _lastId; i++) + delete _windows[i]; +} + +MacWindow *MacWindowManager::addWindow(bool scrollable, bool resizable, bool editable) { + MacWindow *w = new MacWindow(_lastId, scrollable, resizable, editable, this); + + _windows.push_back(w); + _windowStack.push_back(w); + + setActive(_lastId); + + _lastId++; + + return w; +} + +Menu *MacWindowManager::addMenu() { + _menu = new Menu(_lastId, _screen->getBounds(), this); + + _windows.push_back(_menu); + + _lastId++; + + return _menu; +} + +void MacWindowManager::setActive(int id) { + if (_activeWindow == id) + return; + + if (_activeWindow != -1) + _windows[_activeWindow]->setActive(false); + + _activeWindow = id; + + _windows[id]->setActive(true); + + _windowStack.remove(_windows[id]); + _windowStack.push_back(_windows[id]); + + _fullRefresh = true; +} + +void MacWindowManager::removeWindow(MacWindow *target) { + _windowsToRemove.push_back(target); + _needsRemoval = true; +} + +struct PlotData { + Graphics::ManagedSurface *surface; + MacPatterns *patterns; + uint fillType; + int thickness; + + PlotData(Graphics::ManagedSurface *s, MacPatterns *p, int f, int t) : + surface(s), patterns(p), fillType(f), thickness(t) {} +}; + +static void drawPixel(int x, int y, int color, void *data) { + PlotData *p = (PlotData *)data; + + if (p->fillType > p->patterns->size()) + return; + + byte *pat = p->patterns->operator[](p->fillType - 1); + + if (p->thickness == 1) { + if (x >= 0 && x < p->surface->w && y >= 0 && y < p->surface->h) { + uint xu = (uint)x; // for letting compiler optimize it + uint yu = (uint)y; + + *((byte *)p->surface->getBasePtr(xu, yu)) = + (pat[yu % 8] & (1 << (7 - xu % 8))) ? + color : kColorWhite; + } + } else { + int x1 = x; + int x2 = x1 + p->thickness; + int y1 = y; + int y2 = y1 + p->thickness; + + for (y = y1; y < y2; y++) + for (x = x1; x < x2; x++) + if (x >= 0 && x < p->surface->w && y >= 0 && y < p->surface->h) { + uint xu = (uint)x; // for letting compiler optimize it + uint yu = (uint)y; + *((byte *)p->surface->getBasePtr(xu, yu)) = + (pat[yu % 8] & (1 << (7 - xu % 8))) ? + color : kColorWhite; + } + } +} + +void MacWindowManager::drawDesktop() { + Common::Rect r(_screen->getBounds()); + + PlotData pd(_screen, &_patterns, kPatternCheckers, 1); + + Graphics::drawRoundRect(r, kDesktopArc, kColorBlack, true, drawPixel, &pd); + + g_system->copyRectToScreen(_screen->getPixels(), _screen->pitch, 0, 0, _screen->w, _screen->h); +} + +void MacWindowManager::draw() { + assert(_screen); + + removeMarked(); + + if (_fullRefresh) + drawDesktop(); + + for (Common::List<BaseMacWindow *>::const_iterator it = _windowStack.begin(); it != _windowStack.end(); it++) { + BaseMacWindow *w = *it; + if (w->draw(_screen, _fullRefresh)) { + w->setDirty(false); + + Common::Rect clip(w->getDimensions().left - 2, w->getDimensions().top - 2, w->getDimensions().right - 2, w->getDimensions().bottom - 2); + clip.clip(_screen->getBounds()); + + g_system->copyRectToScreen(_screen->getBasePtr(clip.left, clip.top), _screen->pitch, clip.left, clip.top, clip.width(), clip.height()); + } + } + + // Menu is drawn on top of everything and always + if (_menu) + _menu->draw(_screen, _fullRefresh); + + _fullRefresh = false; +} + +bool MacWindowManager::processEvent(Common::Event &event) { + // Menu gets events first fir shortcuts and menu bar + if (_menu && _menu->processEvent(event)) + return true; + + if (event.type != Common::EVENT_MOUSEMOVE && event.type != Common::EVENT_LBUTTONDOWN && + event.type != Common::EVENT_LBUTTONUP) + return false; + + if (_windows[_activeWindow]->isEditable() && _windows[_activeWindow]->getType() == kWindowWindow && + ((MacWindow *)_windows[_activeWindow])->getInnerDimensions().contains(event.mouse.x, event.mouse.y)) { + if (_cursorIsArrow) { + CursorMan.replaceCursor(macCursorBeam, 11, 16, 3, 8, 3); + _cursorIsArrow = false; + } + } else { + if (_cursorIsArrow == false) { + CursorMan.replaceCursor(macCursorArrow, 11, 16, 1, 1, 3); + _cursorIsArrow = true; + } + } + + for (Common::List<BaseMacWindow *>::const_iterator it = _windowStack.end(); it != _windowStack.begin();) { + it--; + BaseMacWindow *w = *it; + + + if (w->hasAllFocus() || w->getDimensions().contains(event.mouse.x, event.mouse.y)) { + if (event.type == Common::EVENT_LBUTTONDOWN || event.type == Common::EVENT_LBUTTONUP) + setActive(w->getId()); + + return w->processEvent(event); + } + } + + return false; +} + +void MacWindowManager::removeMarked() { + if (!_needsRemoval) return; + + Common::List<BaseMacWindow *>::const_iterator it; + for (it = _windowsToRemove.begin(); it != _windowsToRemove.end(); it++) { + removeFromStack(*it); + removeFromWindowList(*it); + delete *it; + _activeWindow = 0; + _fullRefresh = true; + } + _windowsToRemove.clear(); + _needsRemoval = false; +} + +void MacWindowManager::removeFromStack(BaseMacWindow *target) { + Common::List<BaseMacWindow *>::iterator stackIt; + for (stackIt = _windowStack.begin(); stackIt != _windowStack.end(); stackIt++) { + if (*stackIt == target) { + stackIt = _windowStack.erase(stackIt); + stackIt--; + } + } +} + +void MacWindowManager::removeFromWindowList(BaseMacWindow *target) { + int size = _windows.size(); + int ndx = 0; + for (int i = 0; i < size; i++) { + if (_windows[i] == target) { + ndx = i; + } + } + _windows.remove_at(ndx); +} + +////////////////////// +// Font stuff +////////////////////// +void MacWindowManager::loadFonts() { + Common::Archive *dat; + + dat = Common::makeZipArchive("classicmacfonts.dat"); + + if (!dat) { + warning("Could not find classicmacfonts.dat. Falling back to built-in fonts"); + _builtInFonts = true; + + return; + } + + Common::ArchiveMemberList list; + dat->listMembers(list); + + for (Common::ArchiveMemberList::iterator it = list.begin(); it != list.end(); ++it) { + Common::SeekableReadStream *stream = dat->createReadStreamForMember((*it)->getName()); + + Graphics::BdfFont *font = Graphics::BdfFont::loadFont(*stream); + + delete stream; + + Common::String fontName = (*it)->getName(); + + // Trim the .bdf extension + for (int i = fontName.size() - 1; i >= 0; --i) { + if (fontName[i] == '.') { + while ((uint)i < fontName.size()) { + fontName.deleteLastChar(); + } + break; + } + } + + FontMan.assignFontToName(fontName, font); + + debug(2, " %s", fontName.c_str()); + } + + _builtInFonts = false; + + delete dat; +} + +const Graphics::Font *MacWindowManager::getFont(const char *name, Graphics::FontManager::FontUsage fallback) { + const Graphics::Font *font = 0; + + if (!_builtInFonts) { + font = FontMan.getFontByName(name); + + if (!font) + warning("Cannot load font %s", name); + } + + if (_builtInFonts || !font) + font = FontMan.getFontByUsage(fallback); + + return font; +} + +// Source: Apple IIGS Technical Note #41, "Font Family Numbers" +// http://apple2.boldt.ca/?page=til/tn.iigs.041 +static const char *const fontNames[] = { + "Chicago", // system font + "Geneva", // application font + "New York", + "Geneva", + + "Monaco", + "Venice", + "London", + "Athens", + + "San Francisco", + "Toronto", + NULL, + "Cairo", + "Los Angeles", // 12 + + "Zapf Dingbats", + "Bookman", + "Helvetica Narrow", + "Palatino", + NULL, + "Zapf Chancery", + NULL, + + "Times", // 20 + "Helvetica", + "Courier", + "Symbol", + "Taliesin", // mobile? + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, // 30 + NULL, + NULL, + "Avant Garde", + "New Century Schoolbook" +}; + +const char *MacWindowManager::getFontName(int id, int size) { + static char name[128]; + + if (id > ARRAYSIZE(fontNames)) + return NULL; + + snprintf(name, 128, "%s-%d", fontNames[id], size); + + return name; +} + +///////////////// +// Cursor stuff +///////////////// +void MacWindowManager::pushArrowCursor() { + CursorMan.pushCursor(macCursorArrow, 11, 16, 1, 1, 3); +} + +void MacWindowManager::popCursor() { + CursorMan.popCursor(); +} + + +} // End of namespace Graphics diff --git a/graphics/macgui/macwindowmanager.h b/graphics/macgui/macwindowmanager.h new file mode 100644 index 0000000000..cb432d9699 --- /dev/null +++ b/graphics/macgui/macwindowmanager.h @@ -0,0 +1,226 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * MIT License: + * + * Copyright (c) 2009 Alexei Svitkine, Eugene Sandulenko + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following + * conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + */ + +#ifndef GRAPHICS_MACGUI_MACWINDOWMANAGER_H +#define GRAPHICS_MACGUI_MACWINDOWMANAGER_H + +#include "common/array.h" +#include "common/list.h" +#include "common/events.h" +#include "common/archive.h" + +#include "graphics/fontman.h" +#include "graphics/macgui/macwindow.h" + +namespace Graphics { + +namespace MacGUIConstants { +enum { + kDesktopArc = 7 +}; + +enum { + kColorBlack = 0, + kColorGray = 1, + kColorWhite = 2, + kColorGreen = 3, + kColorGreen2 = 4, + kColorCount +}; + +enum { + kPatternSolid = 1, + kPatternStripes = 2, + kPatternCheckers = 3, + kPatternCheckers2 = 4 +}; +} +using namespace MacGUIConstants; + +class ManagedSurface; + +class Menu; + +typedef Common::Array<byte *> MacPatterns; + +/** + * A manager class to handle window creation, destruction, + * drawing, moving and event handling. + */ +class MacWindowManager { +public: + MacWindowManager(); + ~MacWindowManager(); + + /** + * Mutator to indicate the surface onto which the desktop will be drawn. + * Note that this method should be called as soon as the WM is created. + * @param screen Surface on which the desktop will be drawn. + */ + void setScreen(ManagedSurface *screen) { _screen = screen; } + /** + * Accessor method to check the presence of built-in fonts. + * @return True if there are bult-in fonts. + */ + bool hasBuiltInFonts() { return _builtInFonts; } + /** + * Retrieve a font from the available ones. + * @param name Name of the desired font. + * @param fallback Fallback policy in case the desired font isn't there. + * @return The requested font or the fallback. + */ + const Font *getFont(const char *name, FontManager::FontUsage fallback); + + /** + * Return font name from standard ID + * @param id ID of the font + * @param size size of the font + * @return the font name or NULL if ID goes beyond the mapping + */ + const char *getFontName(int id, int size); + + /** + * Create a window with the given parameters. + * Note that this method allocates the necessary memory for the window. + * @param scrollable True if the window has to be scrollable. + * @param resizable True if the window can be resized. + * @param editable True if the window can be edited. + * @return Pointer to the newly created window. + */ + MacWindow *addWindow(bool scrollable, bool resizable, bool editable); + /** + * Add the menu to the desktop. + * Note that the returned menu is empty, and therefore must be filled + * afterwards. + * @return Pointer to a new empty menu. + */ + Menu *addMenu(); + /** + * Set the desired window state to active. + * @param id ID of the window that has to be set to active. + */ + void setActive(int id); + /** + * Mark a window for removal. + * Note that the window data will be destroyed. + * @param target Window to be removed. + */ + void removeWindow(MacWindow *target); + + /** + * Mutator to indicate that the entire desktop must be refreshed. + * @param redraw Currently unused. + */ + void setFullRefresh(bool redraw) { _fullRefresh = true; } + + /** + * Method to draw the desktop into the screen, + * It will take into accout the contents set as dirty. + * Note that this method does not refresh the screen, + * g_system must be called separately. + */ + void draw(); + + /** + * Method to process the events from the engine. + * Most often this method will be called from the engine's GUI, and + * will send the event to the relevant windows for them to process. + * @param event The event to be processed. + * @return True if the event was processed. + */ + bool processEvent(Common::Event &event); + + /** + * Accessor to retrieve an arbitrary window. + * @param id The id of the desired window. + * @return Pointer to the requested window, if it exists. + */ + BaseMacWindow *getWindow(int id) { return _windows[id]; } + + /** + * Retrieve the patterns used to fill surfaces. + * @return A MacPatterns object reference with the patterns. + */ + MacPatterns &getPatterns() { return _patterns; } + void drawFilledRoundRect(ManagedSurface *surface, Common::Rect &rect, int arc, int color); + + void pushArrowCursor(); + void popCursor(); + +private: + void drawDesktop(); + void loadFonts(); + + void removeMarked(); + void removeFromStack(BaseMacWindow *target); + void removeFromWindowList(BaseMacWindow *target); + +private: + ManagedSurface *_screen; + + Common::List<BaseMacWindow *> _windowStack; + Common::Array<BaseMacWindow *> _windows; + + Common::List<BaseMacWindow *> _windowsToRemove; + bool _needsRemoval; + + int _lastId; + int _activeWindow; + + bool _fullRefresh; + + MacPatterns _patterns; + + Menu *_menu; + + bool _builtInFonts; + bool _cursorIsArrow; +}; + +} // End of namespace Graphics + +#endif diff --git a/graphics/managed_surface.cpp b/graphics/managed_surface.cpp new file mode 100644 index 0000000000..273b15de55 --- /dev/null +++ b/graphics/managed_surface.cpp @@ -0,0 +1,260 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "graphics/managed_surface.h" +#include "common/algorithm.h" +#include "common/textconsole.h" + +namespace Graphics { + +const int SCALE_THRESHOLD = 0x100; + +ManagedSurface::ManagedSurface() : + w(_innerSurface.w), h(_innerSurface.h), pitch(_innerSurface.pitch), format(_innerSurface.format), + _disposeAfterUse(DisposeAfterUse::NO), _owner(nullptr) { +} + +ManagedSurface::ManagedSurface(ManagedSurface &surf) : + w(_innerSurface.w), h(_innerSurface.h), pitch(_innerSurface.pitch), format(_innerSurface.format), + _disposeAfterUse(DisposeAfterUse::NO), _owner(nullptr) { + *this = surf; +} + +ManagedSurface::ManagedSurface(int width, int height) : + w(_innerSurface.w), h(_innerSurface.h), pitch(_innerSurface.pitch), format(_innerSurface.format), + _disposeAfterUse(DisposeAfterUse::NO), _owner(nullptr) { + create(width, height); +} + +ManagedSurface::ManagedSurface(int width, int height, const Graphics::PixelFormat &pixelFormat) : + w(_innerSurface.w), h(_innerSurface.h), pitch(_innerSurface.pitch), format(_innerSurface.format), + _disposeAfterUse(DisposeAfterUse::NO), _owner(nullptr) { + create(width, height, pixelFormat); +} + +ManagedSurface::ManagedSurface(ManagedSurface &surf, const Common::Rect &bounds) : + w(_innerSurface.w), h(_innerSurface.h), pitch(_innerSurface.pitch), format(_innerSurface.format), + _disposeAfterUse(DisposeAfterUse::NO), _owner(nullptr) { + create(surf, bounds); +} + +ManagedSurface::~ManagedSurface() { + free(); +} + +ManagedSurface &ManagedSurface::operator=(ManagedSurface &surf) { + // Free any current surface + free(); + + if (surf._disposeAfterUse == DisposeAfterUse::YES) { + // Create a new surface and copy the pixels from the source surface + create(surf.w, surf.h, surf.format); + Common::copy((const byte *)surf.getPixels(), (const byte *)surf.getPixels() + + surf.w * surf.h * surf.format.bytesPerPixel, (byte *)this->getPixels()); + } else { + // Source isn't managed, so simply copy its fields + _owner = surf._owner; + _offsetFromOwner = surf._offsetFromOwner; + void *srcPixels = surf._innerSurface.getPixels(); + _innerSurface.setPixels(srcPixels); + _innerSurface.w = surf.w; + _innerSurface.h = surf.h; + _innerSurface.pitch = surf.pitch; + this->format = surf.format; + } + + return *this; +} + +void ManagedSurface::setPixels(void *newPixels) { + free(); + _innerSurface.setPixels(newPixels); +} + +void ManagedSurface::create(uint16 width, uint16 height) { + create(width, height, PixelFormat::createFormatCLUT8()); +} + +void ManagedSurface::create(uint16 width, uint16 height, const PixelFormat &pixelFormat) { + free(); + _innerSurface.create(width, height, pixelFormat); + + _disposeAfterUse = DisposeAfterUse::YES; + markAllDirty(); +} + +void ManagedSurface::create(ManagedSurface &surf, const Common::Rect &bounds) { + free(); + + _offsetFromOwner = Common::Point(bounds.left, bounds.top); + _innerSurface.setPixels(surf.getBasePtr(bounds.left, bounds.top)); + _innerSurface.pitch = surf.pitch; + _innerSurface.format = surf.format; + _innerSurface.w = bounds.width(); + _innerSurface.h = bounds.height(); + _owner = &surf; + _disposeAfterUse = DisposeAfterUse::NO; +} + +void ManagedSurface::free() { + if (_disposeAfterUse == DisposeAfterUse::YES) + _innerSurface.free(); + + _disposeAfterUse = DisposeAfterUse::NO; + _owner = nullptr; + _offsetFromOwner = Common::Point(0, 0); +} + +bool ManagedSurface::clip(Common::Rect &srcBounds, Common::Rect &destBounds) { + if (destBounds.left >= this->w || destBounds.top >= this->h || + destBounds.right <= 0 || destBounds.bottom <= 0) + return false; + + // Clip the bounds if necessary to fit on-screen + if (destBounds.right > this->w) { + srcBounds.right -= destBounds.right - this->w; + destBounds.right = this->w; + } + + if (destBounds.bottom > this->h) { + srcBounds.bottom -= destBounds.bottom - this->h; + destBounds.bottom = this->h; + } + + if (destBounds.top < 0) { + srcBounds.top += -destBounds.top; + destBounds.top = 0; + } + + if (destBounds.left < 0) { + srcBounds.left += -destBounds.left; + destBounds.left = 0; + } + + return true; +} + +void ManagedSurface::blitFrom(const Surface &src) { + blitFrom(src, Common::Rect(0, 0, src.w, src.h), Common::Point(0, 0)); +} + +void ManagedSurface::blitFrom(const Surface &src, const Common::Point &destPos) { + blitFrom(src, Common::Rect(0, 0, src.w, src.h), destPos); +} + +void ManagedSurface::blitFrom(const Surface &src, const Common::Rect &srcRect, + const Common::Point &destPos) { + Common::Rect srcBounds = srcRect; + Common::Rect destBounds(destPos.x, destPos.y, destPos.x + srcRect.width(), + destPos.y + srcRect.height()); + assert(src.format.bytesPerPixel == format.bytesPerPixel); + + if (!srcRect.isValidRect() || !clip(srcBounds, destBounds)) + return; + + for (int y = 0; y < srcBounds.height(); ++y) { + const byte *srcP = (const byte *)src.getBasePtr(srcBounds.left, srcBounds.top + y); + byte *destP = (byte *)getBasePtr(destBounds.left, destBounds.top + y); + Common::copy(srcP, srcP + srcBounds.width() * format.bytesPerPixel, destP); + } + + addDirtyRect(Common::Rect(0, 0, this->w, this->h)); +} + +void ManagedSurface::transBlitFrom(const Surface &src, uint transColor, bool flipped, uint overrideColor) { + transBlitFrom(src, Common::Rect(0, 0, src.w, src.h), Common::Rect(0, 0, this->w, this->h), + transColor, false, overrideColor); +} + +void ManagedSurface::transBlitFrom(const Surface &src, const Common::Point &destPos, + uint transColor, bool flipped, uint overrideColor) { + transBlitFrom(src, Common::Rect(0, 0, src.w, src.h), Common::Rect(destPos.x, destPos.y, + destPos.x + src.w, destPos.y + src.h), transColor, false, overrideColor); +} + +void ManagedSurface::transBlitFrom(const Surface &src, const Common::Rect &srcRect, + const Common::Point &destPos, uint transColor, bool flipped, uint overrideColor) { + transBlitFrom(src, srcRect, Common::Rect(destPos.x, destPos.y, + destPos.x + src.w, destPos.y + src.h), transColor, false, overrideColor); +} + +template<typename T> +void transBlit(const Surface &src, const Common::Rect &srcRect, Surface *dest, const Common::Rect &destRect, uint transColor, bool flipped, uint overrideColor) { + int scaleX = SCALE_THRESHOLD * srcRect.width() / destRect.width(); + int scaleY = SCALE_THRESHOLD * srcRect.height() / destRect.height(); + + // Loop through drawing output lines + for (int destY = destRect.top, scaleYCtr = 0; destY < destRect.bottom; ++destY, scaleYCtr += scaleY) { + if (destY < 0 || destY >= dest->h) + continue; + const T *srcLine = (const T *)src.getBasePtr(0, scaleYCtr / SCALE_THRESHOLD); + T *destLine = (T *)dest->getBasePtr(destRect.left, destY); + + // Loop through drawing the pixels of the row + for (int destX = destRect.left, xCtr = 0, scaleXCtr = 0; destX < destRect.right; ++destX, ++xCtr, scaleXCtr += scaleX) { + if (destX < 0 || destX >= dest->w) + continue; + + T srcVal = srcLine[flipped ? src.w - scaleXCtr / SCALE_THRESHOLD - 1 : scaleXCtr / SCALE_THRESHOLD]; + if (srcVal != transColor) { + destLine[xCtr] = overrideColor ? overrideColor : srcVal; + } + } + } +} + +void ManagedSurface::transBlitFrom(const Surface &src, const Common::Rect &srcRect, + const Common::Rect &destRect, uint transColor, bool flipped, uint overrideColor) { + if (src.w == 0 || src.h == 0 || destRect.width() == 0 || destRect.height() == 0) + return; + + if (format.bytesPerPixel == 1) + transBlit<byte>(src, srcRect, &_innerSurface, destRect, transColor, flipped, overrideColor); + else if (format.bytesPerPixel == 2) + transBlit<uint16>(src, srcRect, &_innerSurface, destRect, transColor, flipped, overrideColor); + else if (format.bytesPerPixel == 4) + transBlit<uint32>(src, srcRect, &_innerSurface, destRect, transColor, flipped, overrideColor); + else + error("Surface::transBlitFrom: bytesPerPixel must be 1, 2, or 4"); + + // Mark the affected area + addDirtyRect(destRect); +} + +void ManagedSurface::markAllDirty() { + addDirtyRect(Common::Rect(0, 0, this->w, this->h)); +} + +void ManagedSurface::addDirtyRect(const Common::Rect &r) { + if (_owner) { + Common::Rect bounds = r; + bounds.clip(Common::Rect(0, 0, this->w, this->h)); + bounds.translate(_offsetFromOwner.x, _offsetFromOwner.y); + _owner->addDirtyRect(bounds); + } +} + +void ManagedSurface::clear(uint color) { + fillRect(getBounds(), color); +} + +} // End of namespace Graphics diff --git a/graphics/managed_surface.h b/graphics/managed_surface.h new file mode 100644 index 0000000000..8cbd463266 --- /dev/null +++ b/graphics/managed_surface.h @@ -0,0 +1,376 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef GRAPHICS_MANAGED_SURFACE_H +#define GRAPHICS_MANAGED_SURFACE_H + +#include "graphics/pixelformat.h" +#include "graphics/surface.h" +#include "common/rect.h" +#include "common/types.h" + +namespace Graphics { + +class Font; + +/** + * A derived graphics surface, which handles automatically managing the allocated + * surface data block, as well as introducing several new blitting methods + */ +class ManagedSurface { + friend class Font; +private: + /** + * The Graphics::Surface that the managed surface encapsulates + */ + Surface _innerSurface; + + /** + * If set, the inner surface will be freed when the surface is recreated, + * as well as when the surface is destroyed + */ + DisposeAfterUse::Flag _disposeAfterUse; + + /** + * Stores the owning surface if this If this managed surface represents + * a sub-section of another + */ + ManagedSurface *_owner; + + /** + * For sub-section areas of an owning parent managed surface, this represents + * the offset from the parent's top-left corner this sub-surface starts at + */ + Common::Point _offsetFromOwner; +protected: + /** + * Clips the given source bounds so the passed destBounds will be entirely on-screen + */ + bool clip(Common::Rect &srcBounds, Common::Rect &destBounds); + + /** + * Base method that descendent classes can override for recording affected + * dirty areas of the surface + */ + virtual void addDirtyRect(const Common::Rect &r); +public: + uint16 &w; + uint16 &h; + uint16 &pitch; + PixelFormat &format; +public: + /** + * Create the managed surface + */ + ManagedSurface(); + + /** + * Create a managed surface from another one. + * If the source surface is maintaining it's own surface data, then + * this surface will create it's own surface of the same size and copy + * the contents from the source surface + */ + ManagedSurface(ManagedSurface &surf); + + /** + * Create the managed surface + */ + ManagedSurface(int width, int height); + + /** + * Create the managed surface + */ + ManagedSurface(int width, int height, const Graphics::PixelFormat &pixelFormat); + + /** + * Create the managed surface + */ + ManagedSurface(ManagedSurface &surf, const Common::Rect &bounds); + + /** + * Destroy the managed surface + */ + virtual ~ManagedSurface(); + + /** + * Implements automatic conversion to a Graphics::Surface by + * simply returning the inner surface. This must be const, + * because we don't want changes being done directly to it, + * since it would bypass dirty rect handling + */ + operator const Surface &() const { return _innerSurface; } + const Surface &rawSurface() const { return _innerSurface; } + + /** + * Reassign one managed surface to another one + * Note that if the source has a managed surface, it will be duplicated + */ + ManagedSurface &operator=(ManagedSurface &surf); + + /** + * Returns true if the surface has not yet been allocated + */ + bool empty() const { return w == 0 || h == 0 || _innerSurface.getPixels() == nullptr; } + + /** + * Returns true if the surface is managing its own pixels + */ + DisposeAfterUse::Flag disposeAfterUse() const { return _disposeAfterUse; } + + /** + * Return a pointer to the pixel at the specified point. + * + * @param x The x coordinate of the pixel. + * @param y The y coordinate of the pixel. + * @return Pointer to the pixel. + */ + inline const void *getBasePtr(int x, int y) const { + return _innerSurface.getBasePtr(x, y); + } + + /** + * Return a pointer to the pixel at the specified point. + * + * @param x The x coordinate of the pixel. + * @param y The y coordinate of the pixel. + * @return Pointer to the pixel. + */ + inline void *getBasePtr(int x, int y) { + return _innerSurface.getBasePtr(x, y); + } + + /** + * Get a reference to the pixel data + */ + inline void *getPixels() { return _innerSurface.getPixels(); } + inline const void *getPixels() const { return _innerSurface.getPixels(); } + + /** + * Sets the pixel data. + */ + virtual void setPixels(void *newPixels); + + /** + * Allocate memory for the pixel data of the surface. + */ + virtual void create(uint16 width, uint16 height); + + /** + * Allocate memory for the pixel data of the surface. + */ + virtual void create(uint16 width, uint16 height, const PixelFormat &pixelFormat); + + /** + * Sets up the surface as a sub-section of another passed parent surface. This surface + * will not own the pixels, and any dirty rect notifications will automatically be + * passed to the original parent surface. + * @remarks Note that this differs from Graphics::Surface::getSubArea, in that that + * method only adds a single initial dirty rect for the whole area, and then none further + */ + virtual void create(ManagedSurface &surf, const Common::Rect &bounds); + + /** + * Release the memory used by the pixels memory of this surface. This is the + * counterpart to create(). + */ + virtual void free(); + + /** + * Clears any pending dirty rects that have been generated for the surface + */ + virtual void clearDirtyRects() {} + + /** + * When the managed surface is a sub-section of a parent surface, returns the + * the offset in the parent surface that the surface starts at + */ + const Common::Point getOffsetFromOwner() const { return _offsetFromOwner; } + + /** + * Return a rect giving the bounds of the surface + */ + const Common::Rect getBounds() const { + return Common::Rect(0, 0, this->w, this->h); + } + + /** + * Copies another surface into this one + */ + void blitFrom(const Surface &src); + + /** + * Copies another surface into this one at a given destination position + */ + void blitFrom(const Surface &src, const Common::Point &destPos); + + /** + * Copies another surface into this one at a given destination position + */ + void blitFrom(const Surface &src, const Common::Rect &srcRect, + const Common::Point &destPos); + + /** + * Copies another surface into this one ignoring pixels of a designated transparent color + * @param src Source surface + * @param transColor Transparency color to ignore copying + * @param flipped Specifies whether to horizontally flip the image + * @param overrideColor Optional color to use instead of non-transparent pixels from + * the source surface + */ + void transBlitFrom(const Surface &src, uint transColor = 0, bool flipped = false, uint overrideColor = 0); + + /** + * Copies another surface into this one ignoring pixels of a designated transparent color + * @param src Source surface + * @param destPos Destination position to draw the surface + * @param transColor Transparency color to ignore copying + * @param flipped Specifies whether to horizontally flip the image + * @param overrideColor Optional color to use instead of non-transparent pixels from + * the source surface + */ + void transBlitFrom(const Surface &src, const Common::Point &destPos, + uint transColor = 0, bool flipped = false, uint overrideColor = 0); + + /** + * Copies another surface into this one ignoring pixels of a designated transparent color + * @param src Source surface + * @param srcRect Sub-section of source surface to draw + * @param destPos Destination position to draw the surface + * @param transColor Transparency color to ignore copying + * @param flipped Specifies whether to horizontally flip the image + * @param overrideColor Optional color to use instead of non-transparent pixels from + * the source surface + */ + void transBlitFrom(const Surface &src, const Common::Rect &srcRect, const Common::Point &destPos, + uint transColor = 0, bool flipped = false, uint overrideColor = 0); + + /** + * Copies another surface into this one ignoring pixels of a designated transparent color + * @param src Source surface + * @param srcRect Sub-section of source surface to draw + * @param destRect Destination area to draw the surface in. This can be sized differently + * then srcRect, allowing for arbitrary scaling of the image + * @param transColor Transparency color to ignore copying + * @param flipped Specifies whether to horizontally flip the image + * @param overrideColor Optional color to use instead of non-transparent pixels from + * the source surface + */ + void transBlitFrom(const Surface &src, const Common::Rect &srcRect, const Common::Rect &destRect, + uint transColor = 0, bool flipped = false, uint overrideColor = 0); + + /** + * Clear the entire surface + */ + void clear(uint color = 0); + + /** + * Mark the entire surface as dirty + */ + void markAllDirty(); + + /** + * Copies a bitmap to the Surface internal buffer. The pixel format + * of buffer must match the pixel format of the Surface. + */ + void copyRectToSurface(const void *buffer, int srcPitch, int destX, int destY, int width, int height) { + _innerSurface.copyRectToSurface(buffer, srcPitch, destX, destY, width, height); + } + + /** + * Copies a bitmap to the Surface internal buffer. The pixel format + * of buffer must match the pixel format of the Surface. + */ + void copyRectToSurface(const Graphics::Surface &srcSurface, int destX, int destY, const Common::Rect subRect) { + _innerSurface.copyRectToSurface(srcSurface, destX, destY, subRect); + } + + /** + * Copy the data from another Surface, reinitializing the + * surface to match the dimensions of the passed surface + */ + void copyFrom(const ManagedSurface &surf) { + clearDirtyRects(); + _innerSurface.copyFrom(surf._innerSurface); + } + + /** + * Draw a line. + */ + void drawLine(int x0, int y0, int x1, int y1, uint32 color) { + _innerSurface.drawLine(x0, y0, x1, y1, color); + addDirtyRect(Common::Rect(x0, y0, x1, y1)); + } + + /** + * Draw a thick line. + */ + void drawThickLine(int x0, int y0, int x1, int y1, int penX, int penY, uint32 color) { + _innerSurface.drawThickLine(x0, y0, x1, y1, penX, penY, color); + addDirtyRect(Common::Rect(x0, y0, x1 + penX, y1 + penY)); + } + + /** + * Draw a horizontal line. + */ + void hLine(int x, int y, int x2, uint32 color) { + _innerSurface.hLine(x, y, x2, color); + addDirtyRect(Common::Rect(x, y, x2 + 1, y + 1)); + } + + /** + * Draw a vertical line. + */ + void vLine(int x, int y, int y2, uint32 color) { + _innerSurface.vLine(x, y, y2, color); + addDirtyRect(Common::Rect(x, y, x + 1, y2 + 1)); + } + + /** + * Fill a rect with a given color. + */ + void fillRect(Common::Rect r, uint32 color) { + _innerSurface.fillRect(r, color); + addDirtyRect(r); + } + + /** + * Draw a frame around a specified rect. + */ + void frameRect(const Common::Rect &r, uint32 color) { + _innerSurface.frameRect(r, color); + addDirtyRect(r); + } + + /** + * Returns a sub-area of the screen, but only adds a single initial dirty rect + * for the retrieved area. + */ + Surface getSubArea(const Common::Rect &area) { + addDirtyRect(area); + return _innerSurface.getSubArea(area); + } +}; + +} // End of namespace Graphics + + +#endif diff --git a/graphics/module.mk b/graphics/module.mk index 2705322c79..1c87e74ba7 100644 --- a/graphics/module.mk +++ b/graphics/module.mk @@ -12,9 +12,17 @@ MODULE_OBJS := \ fonts/ttf.o \ fonts/winfont.o \ maccursor.o \ + macgui/macmenu.o \ + macgui/macwindow.o \ + macgui/macwindowborder.o \ + macgui/macwindowmanager.o \ + managed_surface.o \ + nine_patch.o \ + pixelformat.o \ primitives.o \ scaler.o \ scaler/thumbnail_intern.o \ + screen.o \ sjis.o \ surface.o \ transform_struct.o \ diff --git a/graphics/nine_patch.cpp b/graphics/nine_patch.cpp new file mode 100644 index 0000000000..fa2ef20a6e --- /dev/null +++ b/graphics/nine_patch.cpp @@ -0,0 +1,359 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/* This code is based on Nine Patch code by Matthew Leverton + taken from https://github.com/konforce/Allegro-Nine-Patch + + Copyright (C) 2011 Matthew Leverton + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do + so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + + +#include "common/array.h" +#include "graphics/transparent_surface.h" +#include "graphics/nine_patch.h" + +#include "graphics/managed_surface.h" + +namespace Graphics { + +NinePatchSide::~NinePatchSide() { + for (uint i = 0; i < _m.size(); i++) + delete _m[i]; + + _m.clear(); +} + + +bool NinePatchSide::init(Graphics::TransparentSurface *bmp, bool vertical) { + const uint len = vertical ? bmp->h : bmp->w; + uint i; + int s, t, z; + + _m.clear(); + + for (i = 1, s = -1, t = 0, z = -1; i < len; ++i) { + int zz; + uint8 r, g, b, a; + uint32 *color = vertical ? (uint32 *)bmp->getBasePtr(0, i) : (uint32 *)bmp->getBasePtr(i, 0); + bmp->format.colorToARGB(*color, a, r, g, b); + + if (i == len - 1) + zz = -1; + else if (r == 0 && g == 0 && b == 0 && a == 255) + zz = 0; + else if (a == 0 || r + g + b + a == 255 * 4) + zz = 1; + else + return false; + + if (z != zz) { + if (s != -1) { + NinePatchMark *mrk = new NinePatchMark; + + mrk->offset = s; + mrk->length = i - s; + if (z == 0) { + mrk->ratio = 1; + t += mrk->length; + } else { + mrk->ratio = 0; + } + _m.push_back(mrk); + } + s = i; + z = zz; + } + } + + _fix = len - 2 - t; + for (i = 0; i < _m.size(); ++i) { + if (_m[i]->ratio) + _m[i]->ratio = _m[i]->length / (float)t; + } + + return true; +} + +void NinePatchSide::calcOffsets(int len) { + uint i, j; + int dest_offset = 0; + int remaining_stretch = len - _fix; + + for (i = 0, j = 0; i < _m.size(); ++i) { + _m[i]->dest_offset = dest_offset; + if (_m[i]->ratio == 0) { + _m[i]->dest_length = _m[i]->length; + } else { + _m[i]->dest_length = (len - _fix) * _m[i]->ratio; + remaining_stretch -= _m[i]->dest_length; + j = i; + } + + dest_offset += _m[i]->dest_length; + } + + if (remaining_stretch) { + _m[j]->dest_length += remaining_stretch; + if (j + 1 < _m.size()) + _m[j + 1]->dest_offset += remaining_stretch; + } +} + +NinePatchBitmap::NinePatchBitmap(Graphics::TransparentSurface *bmp, bool owns_bitmap) { + int i; + uint8 r, g, b, a; + + _bmp = bmp; + _destroy_bmp = owns_bitmap; + _h._m.clear(); + _v._m.clear(); + _cached_dw = 0; + _cached_dh = 0; + _width = bmp->w - 2; + _height = bmp->h - 2; + + if (_width <= 0 || _height <= 0) + goto bad_bitmap; + + /* make sure all four corners are transparent */ +#define _check_pixel(x, y) \ + bmp->format.colorToARGB(*(uint32 *)bmp->getBasePtr(x, y), a, r, g, b); \ + if (a != 0 && r + g + b + a != 4) goto bad_bitmap; + + _check_pixel(0,0); + _check_pixel(bmp->w - 1, 0); + _check_pixel(0, bmp->h - 1); + _check_pixel(bmp->w - 1, bmp->h - 1); +#undef _check_pixel + + _padding.top = _padding.right = _padding.bottom = _padding.left = -1; + + i = 1; + while (i < bmp->w) { + bmp->format.colorToARGB(*(uint32 *)bmp->getBasePtr(i, bmp->h - 1), a, r, g, b); + + if (r + g + b == 0 && a == 1) { + if (_padding.left == -1) + _padding.left = i - 1; + else if (_padding.right != -1) + goto bad_bitmap; + } else if (a == 0 || r + g + b + a == 4) { + if (_padding.left != -1 && _padding.right == -1) + _padding.right = bmp->w - i - 1; + } + ++i; + } + + i = 1; + while (i < bmp->h) { + bmp->format.colorToARGB(*(uint32 *)bmp->getBasePtr(bmp->w - 1, i), a, r, g, b); + + if (r + g + b == 0 && a == 1) { + if (_padding.top == -1) + _padding.top = i - 1; + else if (_padding.bottom != -1) + goto bad_bitmap; + } else if (a == 0 || r + g + b + a == 4) { + if (_padding.top != -1 && _padding.bottom == -1) + _padding.bottom = bmp->h - i - 1; + } + ++i; + } + + if (!_h.init(bmp, false) || !_v.init(bmp, true)) { +bad_bitmap: + _h._m.clear(); + _v._m.clear(); + } +} + +void NinePatchBitmap::blit(Graphics::Surface &target, int dx, int dy, int dw, int dh, byte *palette, byte numColors) { + /* don't draw bitmaps that are smaller than the fixed area */ + if (dw < _h._fix || dh < _v._fix) + return; + + /* if the bitmap is the same size as the origin, then draw it as-is */ + if (dw == _width && dh == _height) { + Common::Rect r(1, 1, dw, dh); + + _bmp->blit(target, dx, dy, Graphics::FLIP_NONE, &r); + return; + } + + /* only recalculate the offsets if they have changed since the last draw */ + if (_cached_dw != dw || _cached_dh != dh) { + _h.calcOffsets(dw); + _v.calcOffsets(dh); + + _cached_dw = dw; + _cached_dh = dh; + } + + /* Handle CLUT8 */ + if (target.format.bytesPerPixel == 1) { + if (!palette) + warning("Trying to blit into a surface with 1bpp, you need the palette."); + + Surface srf; + srf.create(target.w, target.h, _bmp->format); + + drawRegions(srf, dx, dy, dw, dh); + + //TODO: This can be further optimized by keeping the data between draws, + // and using a unique identifier for each palette, so that it only gets + // recalculated when the palette changes. + _cached_colors.clear(); + + for (uint i = 0; i < srf.w; ++i) { + for (uint j = 0; j < srf.h; ++j) { + uint32 color = *(uint32*)srf.getBasePtr(i, j); + if (color > 0) { + *((byte *)target.getBasePtr(i, j)) = closestGrayscale(color, palette, numColors); + } + } + } + + return; + } + + /* Else, draw regions normally */ + drawRegions(target, dx, dy, dw, dh); +} + +NinePatchBitmap::~NinePatchBitmap() { + if (_destroy_bmp) + delete _bmp; +} + +void NinePatchBitmap::drawRegions(Graphics::Surface &target, int dx, int dy, int dw, int dh) { + /* draw each region */ + for (uint i = 0; i < _v._m.size(); ++i) { + for (uint j = 0; j < _h._m.size(); ++j) { + Common::Rect r(_h._m[j]->offset, _v._m[i]->offset, + _h._m[j]->offset + _h._m[j]->length, _v._m[i]->offset + _v._m[i]->length); + + _bmp->blit(target, dx + _h._m[j]->dest_offset, dy + _v._m[i]->dest_offset, + Graphics::FLIP_NONE, &r, TS_ARGB(255, 255, 255, 255), + _h._m[j]->dest_length, _v._m[i]->dest_length); + } + } +} + +void NinePatchBitmap::blitClip(Graphics::Surface &target, Common::Rect clip, int dx, int dy, int dw, int dh) { + /* don't draw bitmaps that are smaller than the fixed area */ + if (dw < _h._fix || dh < _v._fix) + return; + + /* if the bitmap is the same size as the origin, then draw it as-is */ + if (dw == _width && dh == _height) { + Common::Rect r(1, 1, dw, dh); + + _bmp->blitClip(target, clip, dx, dy, Graphics::FLIP_NONE, &r); + return; + } + + /* only recalculate the offsets if they have changed since the last draw */ + if (_cached_dw != dw || _cached_dh != dh) { + _h.calcOffsets(dw); + _v.calcOffsets(dh); + + _cached_dw = dw; + _cached_dh = dh; + } + + /* draw each region */ + for (uint i = 0; i < _v._m.size(); ++i) { + for (uint j = 0; j < _h._m.size(); ++j) { + Common::Rect r(_h._m[j]->offset, _v._m[i]->offset, + _h._m[j]->offset + _h._m[j]->length, _v._m[i]->offset + _v._m[i]->length); + + _bmp->blitClip(target, clip, dx + _h._m[j]->dest_offset, dy + _v._m[i]->dest_offset, + Graphics::FLIP_NONE, &r, TS_ARGB(255, 255, 255, 255), + _h._m[j]->dest_length, _v._m[i]->dest_length); + } + } +} + +byte NinePatchBitmap::getColorIndex(uint32 target, byte* palette) { + byte *pal = palette; + uint i = 0; + uint32 color = TS_RGB(pal[0], pal[1], pal[2]); + while (color != target) { + i += 3; + color = TS_RGB(pal[i], pal[i + 1], pal[i + 2]); + } + return (i / 3); +} + +uint32 NinePatchBitmap::grayscale(uint32 color) { + byte r, g, b; + _bmp->format.colorToRGB(color, r, g, b); + return grayscale(r, g, b); +} + +uint32 NinePatchBitmap::grayscale(byte r, byte g, byte b) { + return (0.29 * r + 0.58 * g + 0.11 * b) / 3; +} + +static inline uint32 dist(uint32 a, uint32 b) { + if (a > b) + return (a - b); + + return b - a; +} + +byte NinePatchBitmap::closestGrayscale(uint32 color, byte* palette, byte paletteLength) { + if (!_cached_colors.contains(color)) { + byte target = grayscale(color); + byte bestNdx = 0; + byte bestColor = grayscale(palette[0], palette[1], palette[2]); + for (byte i = 1; i < paletteLength; ++i) { + byte current = grayscale(palette[i * 3], palette[(i * 3) + 1], palette[(i * 3) + 2]); + if (dist(target, bestColor) >= dist(target, current)) { + bestColor = current; + bestNdx = i; + } + } + _cached_colors[color] = bestNdx; + } + + return _cached_colors[color]; +} + +} // end of namespace Graphics diff --git a/graphics/nine_patch.h b/graphics/nine_patch.h new file mode 100644 index 0000000000..faf44e553f --- /dev/null +++ b/graphics/nine_patch.h @@ -0,0 +1,114 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +/* This code is based on Nine Patch code by Matthew Leverton + taken from https://github.com/konforce/Allegro-Nine-Patch + + Copyright (C) 2011 Matthew Leverton + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in + the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do + so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +#ifndef GRAPHICS_NINE_PATCH_H +#define GRAPHICS_NINE_PATCH_H + +#include "common/array.h" +#include "common/rect.h" +#include "common/hashmap.h" + +namespace Graphics { + +struct TransparentSurface; +struct Surface; + +struct NinePatchMark { + int offset; + int length; + int dest_offset; + int dest_length; + float ratio; +}; + +class NinePatchSide { +public: + Common::Array<NinePatchMark *> _m; + int _fix; + + NinePatchSide() : _fix(0) {} + ~NinePatchSide(); + + bool init(Graphics::TransparentSurface *bmp, bool vertical); + void calcOffsets(int len); +}; + +class NinePatchBitmap { + Graphics::TransparentSurface *_bmp; + NinePatchSide _h, _v; + Common::Rect _padding; + bool _destroy_bmp; + int _width, _height; + int _cached_dw, _cached_dh; + Common::HashMap<uint32, int> _cached_colors; + +public: + NinePatchBitmap(Graphics::TransparentSurface *bmp, bool owns_bitmap); + ~NinePatchBitmap(); + + void blit(Graphics::Surface &target, int dx, int dy, int dw, int dh, byte *palette = NULL, byte numColors = 0); + void blitClip(Graphics::Surface &target, Common::Rect clip, int dx, int dy, int dw, int dh); + + int getWidth() { return _width; } + int getHeight() { return _height; } + int getMinWidth() { return _h._fix; } + int getMinHeight() { return _v._fix; } + Graphics::TransparentSurface *getSource() { return _bmp; } + Common::Rect &getPadding() { return _padding; } + +private: + + void drawRegions(Graphics::Surface &target, int dx, int dy, int dw, int dh); + + // Assumes color is in the palette + byte getColorIndex(uint32 target, byte *palette); + uint32 grayscale(uint32 color); + uint32 grayscale(byte r, byte g, byte b); + byte closestGrayscale(uint32 color, byte* palette, byte paletteLength); +}; + +} // end of namespace Graphics + +#endif // GRAPHICS_NINE_PATCH_H diff --git a/graphics/pixelformat.cpp b/graphics/pixelformat.cpp new file mode 100644 index 0000000000..0a46411254 --- /dev/null +++ b/graphics/pixelformat.cpp @@ -0,0 +1,64 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "graphics/pixelformat.h" +#include "common/algorithm.h" + +namespace Graphics { + +Common::String PixelFormat::toString() const { + if (bytesPerPixel == 1) + return "CLUT8"; + + // We apply a trick to simplify the code here. We encode all the shift, + // loss, and component name in the component entry. By having the shift as + // highest entry we can sort according to shift. + // This works because in valid RGB PixelFormats shift values needs to be + // distinct except when the loss is 8. However, components with loss value + // of 8 are not printed, thus their position does not matter. + int component[4]; + component[0] = (rShift << 16) | (rLoss << 8) | 'R'; + component[1] = (gShift << 16) | (gLoss << 8) | 'G'; + component[2] = (bShift << 16) | (bLoss << 8) | 'B'; + component[3] = (aShift << 16) | (aLoss << 8) | 'A'; + + // Sort components according to descending shift value. + Common::sort(component, component + ARRAYSIZE(component), Common::Greater<int>()); + + Common::String letters, digits; + for (int i = 0; i < ARRAYSIZE(component); ++i) { + const int componentLoss = (component[i] >> 8) & 0xFF; + // A loss of 8 means that the component does not exist. + if (componentLoss == 8) { + continue; + } + + const char componentName = component[i] & 0xFF; + + letters += componentName; + digits += '0' + 8 - componentLoss; + } + + return letters + digits; +} + +} // End of namespace Graphics diff --git a/graphics/pixelformat.h b/graphics/pixelformat.h index 00db6702fc..9dd06241b7 100644 --- a/graphics/pixelformat.h +++ b/graphics/pixelformat.h @@ -24,6 +24,7 @@ #define GRAPHICS_PIXELFORMAT_H #include "common/scummsys.h" +#include "common/str.h" namespace Graphics { @@ -260,6 +261,8 @@ struct PixelFormat { // Unsupported return 0; } + + Common::String toString() const; }; } // End of namespace Graphics diff --git a/graphics/primitives.cpp b/graphics/primitives.cpp index 564bdb9673..8663a61606 100644 --- a/graphics/primitives.cpp +++ b/graphics/primitives.cpp @@ -20,7 +20,9 @@ * */ +#include "common/algorithm.h" #include "common/util.h" +#include "graphics/primitives.h" namespace Graphics { @@ -62,6 +64,22 @@ void drawLine(int x0, int y0, int x1, int y1, int color, void (*plotProc)(int, i } } +void drawHLine(int x1, int x2, int y, int color, void (*plotProc)(int, int, int, void *), void *data) { + if (x1 > x2) + SWAP(x1, x2); + + for (int x = x1; x <= x2; x++) + (*plotProc)(x, y, color, data); +} + +void drawVLine(int x, int y1, int y2, int color, void (*plotProc)(int, int, int, void *), void *data) { + if (y1 > y2) + SWAP(y1, y2); + + for (int y = y1; y <= y2; y++) + (*plotProc)(x, y, color, data); +} + void drawThickLine(int x0, int y0, int x1, int y1, int penX, int penY, int color, void (*plotProc)(int, int, int, void *), void *data) { assert(penX > 0 && penY > 0); @@ -79,4 +97,333 @@ void drawThickLine(int x0, int y0, int x1, int y1, int penX, int penY, int color drawLine(x0 + x, y0 + y, x1 + x, y1 + y, color, plotProc, data); } +/* Bresenham as presented in Foley & Van Dam */ +/* Code is based on GD lib http://libgd.github.io/ */ +void drawThickLine2(int x1, int y1, int x2, int y2, int thick, int color, void (*plotProc)(int, int, int, void *), void *data) { + int incr1, incr2, d, x, y, xend, yend, xdirflag, ydirflag; + int wid; + int w, wstart; + + int dx = abs(x2 - x1); + int dy = abs(y2 - y1); + + if (dx == 0) { + int xn = x1 - thick / 2; + Common::Rect r(xn, MIN(y1, y2), xn + thick - 1, MAX(y1, y2)); + drawFilledRect(r, color, plotProc, data); + return; + } else if (dy == 0) { + int yn = y1 - thick / 2; + Common::Rect r(MIN(x1, x2), yn, MAX(x1, x2), yn + thick - 1); + drawFilledRect(r, color, plotProc, data); + return; + } + + if (dy <= dx) { + /* More-or-less horizontal. use wid for vertical stroke */ + /* Doug Claar: watch out for NaN in atan2 (2.0.5) */ + + /* 2.0.12: Michael Schwartz: divide rather than multiply; + TBB: but watch out for /0! */ + double ac = cos(atan2((double)dy, (double)dx)); + if (ac != 0) { + wid = thick / ac; + } else { + wid = 1; + } + if (wid == 0) { + wid = 1; + } + d = 2 * dy - dx; + incr1 = 2 * dy; + incr2 = 2 * (dy - dx); + if (x1 > x2) { + x = x2; + y = y2; + ydirflag = (-1); + xend = x1; + } else { + x = x1; + y = y1; + ydirflag = 1; + xend = x2; + } + + /* Set up line thickness */ + wstart = y - wid / 2; + for (w = wstart; w < wstart + wid; w++) + (*plotProc)(x, y, color, data); + + if (((y2 - y1) * ydirflag) > 0) { + while (x < xend) { + x++; + if (d < 0) { + d += incr1; + } else { + y++; + d += incr2; + } + wstart = y - wid / 2; + for (w = wstart; w < wstart + wid; w++) + (*plotProc)(x, w, color, data); + } + } else { + while (x < xend) { + x++; + if (d < 0) { + d += incr1; + } else { + y--; + d += incr2; + } + wstart = y - wid / 2; + for (w = wstart; w < wstart + wid; w++) + (*plotProc)(x, w, color, data); + } + } + } else { + /* More-or-less vertical. use wid for horizontal stroke */ + /* 2.0.12: Michael Schwartz: divide rather than multiply; + TBB: but watch out for /0! */ + double as = sin(atan2((double)dy, (double)dx)); + if (as != 0) { + wid = thick / as; + } else { + wid = 1; + } + if (wid == 0) + wid = 1; + + d = 2 * dx - dy; + incr1 = 2 * dx; + incr2 = 2 * (dx - dy); + if (y1 > y2) { + y = y2; + x = x2; + yend = y1; + xdirflag = (-1); + } else { + y = y1; + x = x1; + yend = y2; + xdirflag = 1; + } + + /* Set up line thickness */ + wstart = x - wid / 2; + for (w = wstart; w < wstart + wid; w++) + (*plotProc)(w, y, color, data); + + if (((x2 - x1) * xdirflag) > 0) { + while (y < yend) { + y++; + if (d < 0) { + d += incr1; + } else { + x++; + d += incr2; + } + wstart = x - wid / 2; + for (w = wstart; w < wstart + wid; w++) + (*plotProc)(w, y, color, data); + } + } else { + while (y < yend) { + y++; + if (d < 0) { + d += incr1; + } else { + x--; + d += incr2; + } + wstart = x - wid / 2; + for (w = wstart; w < wstart + wid; w++) + (*plotProc)(w, y, color, data); + } + } + } +} + +void drawFilledRect(Common::Rect &rect, int color, void (*plotProc)(int, int, int, void *), void *data) { + for (int y = rect.top; y <= rect.bottom; y++) + drawHLine(rect.left, rect.right, y, color, plotProc, data); +} + +// http://members.chello.at/easyfilter/bresenham.html +void drawRoundRect(Common::Rect &rect, int arc, int color, bool filled, void (*plotProc)(int, int, int, void *), void *data) { + if (rect.height() < rect.width()) { + int x = -arc, y = 0, err = 2-2*arc; /* II. Quadrant */ + int dy = rect.height() - arc * 2; + int r = arc; + int stop = 0; + int lastx = 0, lasty = 0; + if (dy < 0) + stop = -dy / 2; + + do { + if (filled) { + drawHLine(rect.left+x+r, rect.right-x-r, rect.top-y+r-stop, color, plotProc, data); + drawHLine(rect.left+x+r, rect.right-x-r, rect.bottom+y-r+stop, color, plotProc, data); + } else { + (*plotProc)(rect.left+x+r, rect.top-y+r-stop, color, data); + (*plotProc)(rect.right-x-r, rect.top-y+r-stop, color, data); + (*plotProc)(rect.left+x+r, rect.bottom+y-r+stop, color, data); + (*plotProc)(rect.right-x-r, rect.bottom+y-r+stop, color, data); + + lastx = x; + lasty = y; + } + arc = err; + if (arc <= y) err += ++y*2+1; /* e_xy+e_y < 0 */ + if (arc > x || err > y) err += ++x*2+1; /* e_xy+e_x > 0 or no 2nd y-step */ + if (stop && y > stop) + break; + } while (x < 0); + + if (!filled) { + x = lastx; + y = lasty; + + drawHLine(rect.left+x+r, rect.right-x-r, rect.top-y+r-stop, color, plotProc, data); + drawHLine(rect.left+x+r, rect.right-x-r, rect.bottom+y-r+stop, color, plotProc, data); + } + + for (int i = 0; i < dy; i++) { + if (filled) { + drawHLine(rect.left, rect.right, rect.top + r + i, color, plotProc, data); + } else { + (*plotProc)(rect.left, rect.top + r + i, color, data); + (*plotProc)(rect.right, rect.top + r + i, color, data); + } + } + } else { + int y = -arc, x = 0, err = 2-2*arc; /* II. Quadrant */ + int dx = rect.width() - arc * 2; + int r = arc; + int stop = 0; + int lastx = 0, lasty = 0; + if (dx < 0) + stop = -dx / 2; + + do { + if (filled) { + drawVLine(rect.left-x+r-stop, rect.top+y+r, rect.bottom-y-r, color, plotProc, data); + drawVLine(rect.right+x-r+stop, rect.top+y+r, rect.bottom-y-r, color, plotProc, data); + } else { + (*plotProc)(rect.left-x+r-stop, rect.top+y+r, color, data); + (*plotProc)(rect.left-x+r-stop, rect.bottom-y-r, color, data); + (*plotProc)(rect.right+x-r+stop, rect.top+y+r, color, data); + (*plotProc)(rect.right+x-r+stop, rect.bottom-y-r, color, data); + + lastx = x; + lasty = y; + } + + arc = err; + if (arc <= x) err += ++x*2+1; /* e_xy+e_y < 0 */ + if (arc > y || err > x) err += ++y*2+1; /* e_xy+e_x > 0 or no 2nd y-step */ + if (stop && x > stop) + break; + } while (y < 0); + + if (!filled) { + x = lastx; + y = lasty; + drawVLine(rect.left-x+r-stop, rect.top+y+r, rect.bottom-y-r, color, plotProc, data); + drawVLine(rect.right+x-r+stop, rect.top+y+r, rect.bottom-y-r, color, plotProc, data); + } + + for (int i = 0; i < dx; i++) { + if (filled) { + drawVLine(rect.left + r + i, rect.top, rect.bottom, color, plotProc, data); + } else { + (*plotProc)(rect.left + r + i, rect.top, color, data); + (*plotProc)(rect.left + r + i, rect.bottom, color, data); + } + } + } +} + +// Based on public-domain code by Darel Rex Finley, 2007 +// http://alienryderflex.com/polygon_fill/ +void drawPolygonScan(int *polyX, int *polyY, int npoints, Common::Rect &bbox, int color, void (*plotProc)(int, int, int, void *), void *data) { + int *nodeX = (int *)calloc(npoints, sizeof(int)); + int i, j; + + // Loop through the rows of the image. + for (int pixelY = bbox.top; pixelY < bbox.bottom; pixelY++) { + // Build a list of nodes. + int nodes = 0; + j = npoints - 1; + + for (i = 0; i < npoints; i++) { + if ((polyY[i] < pixelY && polyY[j] >= pixelY) || (polyY[j] < pixelY && polyY[i] >= pixelY)) { + nodeX[nodes++] = (int)(polyX[i] + (double)(pixelY - polyY[i]) / (double)(polyY[j]-polyY[i]) * + (double)(polyX[j] - polyX[i]) + 0.5); + } + j = i; + } + + // Sort the nodes + Common::sort(nodeX, &nodeX[nodes]); + + // Fill the pixels between node pairs. + for (i = 0; i < nodes; i += 2) { + if (nodeX[i ] >= bbox.right) + break; + if (nodeX[i + 1] > bbox.left) { + nodeX[i] = MAX<int16>(nodeX[i], bbox.left); + nodeX[i + 1] = MIN<int16>(nodeX[i + 1], bbox.right); + + drawHLine(nodeX[i], nodeX[i + 1], pixelY, color, plotProc, data); + } + } + } + + free(nodeX); +} + +// http://members.chello.at/easyfilter/bresenham.html +void drawEllipse(int x0, int y0, int x1, int y1, int color, bool filled, void (*plotProc)(int, int, int, void *), void *data) { + int a = abs(x1-x0), b = abs(y1-y0), b1 = b&1; /* values of diameter */ + long dx = 4*(1-a)*b*b, dy = 4*(b1+1)*a*a; /* error increment */ + long err = dx+dy+b1*a*a, e2; /* error of 1.step */ + + if (x0 > x1) { x0 = x1; x1 += a; } /* if called with swapped points */ + if (y0 > y1) y0 = y1; /* .. exchange them */ + y0 += (b+1)/2; y1 = y0-b1; /* starting pixel */ + a *= 8*a; b1 = 8*b*b; + + do { + if (filled) { + drawHLine(x0, x1, y0, color, plotProc, data); + drawHLine(x0, x1, y1, color, plotProc, data); + } else { + (*plotProc)(x1, y0, color, data); /* I. Quadrant */ + (*plotProc)(x0, y0, color, data); /* II. Quadrant */ + (*plotProc)(x0, y1, color, data); /* III. Quadrant */ + (*plotProc)(x1, y1, color, data); /* IV. Quadrant */ + } + e2 = 2*err; + if (e2 <= dy) { y0++; y1--; err += dy += a; } /* y step */ + if (e2 >= dx || 2*err > dy) { x0++; x1--; err += dx += b1; } /* x step */ + } while (x0 <= x1); + + while (y0-y1 < b) { /* too early stop of flat ellipses a=1 */ + if (filled) { + drawHLine(x0-1, x0-1, y0, color, plotProc, data); /* -> finish tip of ellipse */ + drawHLine(x1+1, x1+1, y0, color, plotProc, data); + drawHLine(x0-1, x0-1, y1, color, plotProc, data); + drawHLine(x1+1, x1+1, y1, color, plotProc, data); + } else { + (*plotProc)(x0-1, y0, color, data); /* -> finish tip of ellipse */ + (*plotProc)(x1+1, y0, color, data); + (*plotProc)(x0-1, y1, color, data); + (*plotProc)(x1+1, y1, color, data); + } + y0++; + y1--; + } +} + } // End of namespace Graphics diff --git a/graphics/primitives.h b/graphics/primitives.h index a3e8ab1565..62dc10bfdf 100644 --- a/graphics/primitives.h +++ b/graphics/primitives.h @@ -23,10 +23,21 @@ #ifndef GRAPHICS_PRIMITIVES_H #define GRAPHICS_PRIMITIVES_H +#include "common/rect.h" + namespace Graphics { void drawLine(int x0, int y0, int x1, int y1, int color, void (*plotProc)(int, int, int, void *), void *data); +void drawHLine(int x1, int x2, int y, int color, void (*plotProc)(int, int, int, void *), void *data); +void drawVLine(int x, int y1, int y2, int color, void (*plotProc)(int, int, int, void *), void *data); void drawThickLine(int x0, int y0, int x1, int y1, int penX, int penY, int color, void (*plotProc)(int, int, int, void *), void *data); +void drawThickLine2(int x1, int y1, int x2, int y2, int thick, int color, + void (*plotProc)(int, int, int, void *), void *data); +void drawFilledRect(Common::Rect &rect, int color, void (*plotProc)(int, int, int, void *), void *data); +void drawRoundRect(Common::Rect &rect, int arc, int color, bool filled, void (*plotProc)(int, int, int, void *), void *data); +void drawPolygonScan(int *polyX, int *polyY, int npoints, Common::Rect &bbox, int color, + void (*plotProc)(int, int, int, void *), void *data); +void drawEllipse(int x0, int y0, int x1, int y1, int color, bool filled, void (*plotProc)(int, int, int, void *), void *data); } // End of namespace Graphics diff --git a/graphics/scaler/thumbnail_intern.cpp b/graphics/scaler/thumbnail_intern.cpp index 65e327f9c2..78fb7828d7 100644 --- a/graphics/scaler/thumbnail_intern.cpp +++ b/graphics/scaler/thumbnail_intern.cpp @@ -171,7 +171,8 @@ static bool grabScreen565(Graphics::Surface *surf) { if (!screen) return false; - assert(screen->format.bytesPerPixel == 1 || screen->format.bytesPerPixel == 2); + assert(screen->format.bytesPerPixel == 1 || screen->format.bytesPerPixel == 2 + || screen->format.bytesPerPixel == 4); assert(screen->getPixels() != 0); Graphics::PixelFormat screenFormat = g_system->getScreenFormat(); @@ -197,6 +198,9 @@ static bool grabScreen565(Graphics::Surface *surf) { } else if (screenFormat.bytesPerPixel == 2) { uint16 col = READ_UINT16(screen->getBasePtr(x, y)); screenFormat.colorToRGB(col, r, g, b); + } else if (screenFormat.bytesPerPixel == 4) { + uint32 col = READ_UINT32(screen->getBasePtr(x, y)); + screenFormat.colorToRGB(col, r, g, b); } *((uint16 *)surf->getBasePtr(x, y)) = Graphics::RGBToColor<Graphics::ColorMasks<565> >(r, g, b); diff --git a/graphics/screen.cpp b/graphics/screen.cpp new file mode 100644 index 0000000000..ccf651f536 --- /dev/null +++ b/graphics/screen.cpp @@ -0,0 +1,129 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/system.h" +#include "common/algorithm.h" +#include "graphics/screen.h" +#include "graphics/palette.h" + +namespace Graphics { + +Screen::Screen(): ManagedSurface() { + create(g_system->getWidth(), g_system->getHeight(), g_system->getScreenFormat()); +} + +Screen::Screen(int width, int height): ManagedSurface() { + create(width, height); +} + +Screen::Screen(int width, int height, PixelFormat pixelFormat): ManagedSurface() { + create(width, height, pixelFormat); +} + +void Screen::update() { + // Merge the dirty rects + mergeDirtyRects(); + + // Loop through copying dirty areas to the physical screen + Common::List<Common::Rect>::iterator i; + for (i = _dirtyRects.begin(); i != _dirtyRects.end(); ++i) { + const Common::Rect &r = *i; + const byte *srcP = (const byte *)getBasePtr(r.left, r.top); + g_system->copyRectToScreen(srcP, pitch, r.left, r.top, + r.width(), r.height()); + } + + // Signal the physical screen to update + g_system->updateScreen(); + _dirtyRects.clear(); +} + + +void Screen::addDirtyRect(const Common::Rect &r) { + Common::Rect bounds = r; + bounds.clip(getBounds()); + bounds.translate(getOffsetFromOwner().x, getOffsetFromOwner().y); + + if (bounds.width() > 0 && bounds.height() > 0) + _dirtyRects.push_back(bounds); +} + +void Screen::makeAllDirty() { + addDirtyRect(Common::Rect(0, 0, this->w, this->h)); +} + +void Screen::mergeDirtyRects() { + Common::List<Common::Rect>::iterator rOuter, rInner; + + // Process the dirty rect list to find any rects to merge + for (rOuter = _dirtyRects.begin(); rOuter != _dirtyRects.end(); ++rOuter) { + rInner = rOuter; + while (++rInner != _dirtyRects.end()) { + + if ((*rOuter).intersects(*rInner)) { + // These two rectangles overlap, so merge them + unionRectangle(*rOuter, *rOuter, *rInner); + + // remove the inner rect from the list + _dirtyRects.erase(rInner); + + // move back to beginning of list + rInner = rOuter; + } + } + } +} + +bool Screen::unionRectangle(Common::Rect &destRect, const Common::Rect &src1, const Common::Rect &src2) { + destRect = src1; + destRect.extend(src2); + + return !destRect.isEmpty(); +} + +void Screen::getPalette(byte palette[PALETTE_SIZE]) { + assert(format.bytesPerPixel == 1); + g_system->getPaletteManager()->grabPalette(palette, 0, PALETTE_COUNT); +} + +void Screen::getPalette(byte *palette, uint start, uint num) { + assert(format.bytesPerPixel == 1); + g_system->getPaletteManager()->grabPalette(palette, start, num); +} + +void Screen::setPalette(const byte palette[PALETTE_SIZE]) { + assert(format.bytesPerPixel == 1); + g_system->getPaletteManager()->setPalette(palette, 0, PALETTE_COUNT); +} + +void Screen::setPalette(const byte *palette, uint start, uint num) { + assert(format.bytesPerPixel == 1); + g_system->getPaletteManager()->setPalette(palette, start, num); +} + +void Screen::clearPalette() { + byte palette[PALETTE_SIZE]; + Common::fill(&palette[0], &palette[PALETTE_SIZE], 0); + setPalette(palette); +} + +} // End of namespace Graphics diff --git a/graphics/screen.h b/graphics/screen.h new file mode 100644 index 0000000000..b3bb2d3eb2 --- /dev/null +++ b/graphics/screen.h @@ -0,0 +1,118 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef GRAPHICS_SCREEN_H +#define GRAPHICS_SCREEN_H + +#include "graphics/managed_surface.h" +#include "graphics/pixelformat.h" +#include "common/list.h" +#include "common/rect.h" + +namespace Graphics { + +#define PALETTE_COUNT 256 +#define PALETTE_SIZE (256 * 3) + +/** + * Implements a specialised surface that represents the screen. + * It keeps track of any areas of itself that are updated by drawing + * calls, and provides an update that method that blits the affected + * areas to the physical screen + */ +class Screen : public ManagedSurface { +private: + /** + * List of affected areas of the screen + */ + Common::List<Common::Rect> _dirtyRects; +private: + /** + * Merges together overlapping dirty areas of the screen + */ + void mergeDirtyRects(); + + /** + * Returns the union of two dirty area rectangles + */ + bool unionRectangle(Common::Rect &destRect, const Common::Rect &src1, const Common::Rect &src2); +protected: + /** + * Adds a rectangle to the list of modified areas of the screen during the + * current frame + */ + virtual void addDirtyRect(const Common::Rect &r); +public: + Screen(); + Screen(int width, int height); + Screen(int width, int height, PixelFormat pixelFormat); + + /** + * Returns true if there are any pending screen updates (dirty areas) + */ + bool isDirty() const { return !_dirtyRects.empty(); } + + /** + * Marks the whole screen as dirty. This forces the next call to update + * to copy the entire screen contents + */ + void makeAllDirty(); + + /** + * Clear the current dirty rects list + */ + virtual void clearDirtyRects() { _dirtyRects.clear(); } + + /** + * Updates the screen by copying any affected areas to the system + */ + virtual void update(); + + /** + * Return the currently active palette + */ + void getPalette(byte palette[PALETTE_SIZE]); + + /** + * Return a portion of the currently active palette + */ + void getPalette(byte *palette, uint start, uint num); + + /** + * Set the palette + */ + void setPalette(const byte palette[PALETTE_SIZE]); + + /** + * Set a subsection of the palette + */ + void setPalette(const byte *palette, uint start, uint num); + + /** + * Clears the current palette, setting all entries to black + */ + void clearPalette(); +}; + +} // End of namespace Graphics + +#endif diff --git a/graphics/surface.cpp b/graphics/surface.cpp index 67ed942b0b..699e1ccd22 100644 --- a/graphics/surface.cpp +++ b/graphics/surface.cpp @@ -498,4 +498,109 @@ Graphics::Surface *Surface::convertTo(const PixelFormat &dstFormat, const byte * return surface; } +FloodFill::FloodFill(Graphics::Surface *surface, uint32 oldColor, uint32 fillColor, bool maskMode) { + _surface = surface; + _oldColor = oldColor; + _fillColor = fillColor; + _w = surface->w; + _h = surface->h; + + _mask = nullptr; + _maskMode = maskMode; + + if (_maskMode) { + _mask = new Graphics::Surface(); + _mask->create(_w, _h, Graphics::PixelFormat::createFormatCLUT8()); // Uses calloc() + } + + _visited = (byte *)calloc(_w * _h, 1); +} + +FloodFill::~FloodFill() { + while(!_queue.empty()) { + Common::Point *p = _queue.front(); + + delete p; + _queue.pop_front(); + } + + free(_visited); + + if (_mask) + delete _mask; +} + +void FloodFill::addSeed(int x, int y) { + if (x >= 0 && x < _w && y >= 0 && y < _h) { + if (!_visited[y * _w + x]) { + _visited[y * _w + x] = 1; + void *src = _surface->getBasePtr(x, y); + void *dst; + bool changed = false; + + if (_maskMode) + dst = _mask->getBasePtr(x, y); + else + dst = src; + + if (_surface->format.bytesPerPixel == 1) { + if (*((byte *)src) == _oldColor) { + *((byte *)dst) = _maskMode ? 255 : _fillColor; + changed = true; + } + } else if (_surface->format.bytesPerPixel == 2) { + if (READ_UINT16(src) == _oldColor) { + if (!_maskMode) + WRITE_UINT16(src, _fillColor); + else + *((byte *)dst) = 255; + + changed = true; + } + } else if (_surface->format.bytesPerPixel == 4) { + if (READ_UINT32(src) == _oldColor) { + if (!_maskMode) + WRITE_UINT32(src, _fillColor); + else + *((byte *)dst) = 255; + + changed = true; + } + } else { + error("Unsupported bpp in FloodFill"); + } + + if (changed) { + Common::Point *pt = new Common::Point(x, y); + + _queue.push_back(pt); + } + } + } +} + +void FloodFill::fill() { + while (!_queue.empty()) { + Common::Point *p = _queue.front(); + _queue.pop_front(); + addSeed(p->x , p->y - 1); + addSeed(p->x - 1, p->y ); + addSeed(p->x , p->y + 1); + addSeed(p->x + 1, p->y ); + + delete p; + } +} + +void FloodFill::fillMask() { + _maskMode = true; + + if (!_mask) { + _mask = new Graphics::Surface(); + _mask->create(_w, _h, Graphics::PixelFormat::createFormatCLUT8()); // Uses calloc() + } + + fill(); +} + } // End of namespace Graphics diff --git a/graphics/surface.h b/graphics/surface.h index aaa386b168..87c5f52503 100644 --- a/graphics/surface.h +++ b/graphics/surface.h @@ -24,9 +24,11 @@ #define GRAPHICS_SURFACE_H #include "common/scummsys.h" +#include "common/list.h" namespace Common { struct Rect; +struct Point; } #include "graphics/pixelformat.h" @@ -341,6 +343,72 @@ struct SharedPtrSurfaceDeleter { } }; +/** + * Stack-based flood fill algorithm for arbitrary Surfaces. + * + * It could be used in 2 ways. One is to fill the pixels of oldColor + * with fillColor. Second is when the surface stays intact but another + * surface with mask is created, where filled colors are marked with 255. + * + * Before running fill() or fillMask(), the initial pixels must be addSeed + * with addSeed() method. + */ +class FloodFill { +public: + /** + * Construct a simple Surface object. + * + * @param surface Input surface + * @param oldColor Color on the surface to change + * @param fillColor Color to fill with + */ + FloodFill(Surface *surface, uint32 oldColor, uint32 fillColor, bool maskMode = false); + ~FloodFill(); + + /** + * Add pixels to the fill queue. + * + * @param x The x coordinate of the pixel. + * @param y The x coordinate of the pixel. + */ + void addSeed(int x, int y); + + /** + * Fill the surface as requested. + * + * It uses pixels which were added with addSeed() method. + * + * @see addSeed + */ + void fill(); + + /** + * Fill the mask. The mask is a CLUT8 Surface with pixels 0 and 255. + * 255 means that the pixel has been filled. + * + * It uses pixels which were added with addSeed() method. + * + * @see addSeed + */ + void fillMask(); + + /** + * Get the resulting mask. + * + * @see fillMask + */ + Surface *getMask() { return _mask; } + +private: + Common::List<Common::Point *> _queue; + Surface *_surface; + Surface *_mask; + uint32 _oldColor, _fillColor; + byte *_visited; + int _w, _h; + + bool _maskMode; +}; } // End of namespace Graphics diff --git a/graphics/thumbnail.cpp b/graphics/thumbnail.cpp index 802ad09cbb..a3037e5ad5 100644 --- a/graphics/thumbnail.cpp +++ b/graphics/thumbnail.cpp @@ -67,7 +67,7 @@ HeaderState loadHeader(Common::SeekableReadStream &in, ThumbnailHeader &header, header.size = in.readUint32BE(); header.version = in.readByte(); - // Do a check whether any read errors had occured. If so we cannot use the + // Do a check whether any read errors had occurred. 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 diff --git a/graphics/transparent_surface.cpp b/graphics/transparent_surface.cpp index f6e8cacb8b..81b7f3d647 100644 --- a/graphics/transparent_surface.cpp +++ b/graphics/transparent_surface.cpp @@ -41,8 +41,6 @@ namespace Graphics { -static const int kAShift = 0;//img->format.aShift; - static const int kBModShift = 0;//img->format.bShift; static const int kGModShift = 8;//img->format.gShift; static const int kRModShift = 16;//img->format.rShift; @@ -118,7 +116,7 @@ void doBlitBinaryFast(byte *ino, byte *outo, uint32 width, uint32 height, uint32 in = ino; for (uint32 j = 0; j < width; j++) { uint32 pix = *(uint32 *)in; - int a = (pix >> kAShift) & 0xff; + int a = in[kAIndex]; if (a != 0) { // Full opacity (Any value not exactly 0 is Opaque here) *(uint32 *)out = pix; @@ -338,7 +336,7 @@ Common::Rect TransparentSurface::blit(Graphics::Surface &target, int posX, int p retSize.setWidth(0); retSize.setHeight(0); // Check if we need to draw anything at all - int ca = (color >> 24) & 0xff; + int ca = (color >> kAModShift) & 0xff; if (ca == 0) { return retSize; @@ -348,7 +346,7 @@ Common::Rect TransparentSurface::blit(Graphics::Surface &target, int posX, int p TransparentSurface srcImage(*this, false); // TODO: Is the data really in the screen format? if (format.bytesPerPixel != 4) { - warning("TransparentSurface can only blit 32bpp images, but got %d", format.bytesPerPixel * 8); + warning("TransparentSurface can only blit 32bpp images, but got %d", format.bytesPerPixel * 8); return retSize; } @@ -464,6 +462,139 @@ Common::Rect TransparentSurface::blit(Graphics::Surface &target, int posX, int p return retSize; } +Common::Rect TransparentSurface::blitClip(Graphics::Surface &target, Common::Rect clippingArea, int posX, int posY, int flipping, Common::Rect *pPartRect, uint color, int width, int height, TSpriteBlendMode blendMode) { + Common::Rect retSize; + retSize.top = 0; + retSize.left = 0; + retSize.setWidth(0); + retSize.setHeight(0); + // Check if we need to draw anything at all + int ca = (color >> kAModShift) & 0xff; + + if (ca == 0) { + return retSize; + } + + // Create an encapsulating surface for the data + TransparentSurface srcImage(*this, false); + // TODO: Is the data really in the screen format? + if (format.bytesPerPixel != 4) { + warning("TransparentSurface can only blit 32bpp images, but got %d", format.bytesPerPixel * 8); + return retSize; + } + + if (pPartRect) { + + int xOffset = pPartRect->left; + int yOffset = pPartRect->top; + + if (flipping & FLIP_V) { + yOffset = srcImage.h - pPartRect->bottom; + } + + if (flipping & FLIP_H) { + xOffset = srcImage.w - pPartRect->right; + } + + srcImage.pixels = getBasePtr(xOffset, yOffset); + srcImage.w = pPartRect->width(); + srcImage.h = pPartRect->height(); + + debug(6, "Blit(%d, %d, %d, [%d, %d, %d, %d], %08x, %d, %d)", posX, posY, flipping, + pPartRect->left, pPartRect->top, pPartRect->width(), pPartRect->height(), color, width, height); + } else { + + debug(6, "Blit(%d, %d, %d, [%d, %d, %d, %d], %08x, %d, %d)", posX, posY, flipping, 0, 0, + srcImage.w, srcImage.h, color, width, height); + } + + if (width == -1) { + width = srcImage.w; + } + if (height == -1) { + height = srcImage.h; + } + +#ifdef SCALING_TESTING + // Hardcode scaling to 66% to test scaling + width = width * 2 / 3; + height = height * 2 / 3; +#endif + + Graphics::Surface *img = nullptr; + Graphics::Surface *imgScaled = nullptr; + byte *savedPixels = nullptr; + if ((width != srcImage.w) || (height != srcImage.h)) { + // Scale the image + img = imgScaled = srcImage.scale(width, height); + savedPixels = (byte *)img->getPixels(); + } else { + img = &srcImage; + } + + // Handle off-screen clipping + if (posY < clippingArea.top) { + img->h = MAX(0, (int)img->h - (clippingArea.top - posY)); + img->setPixels((byte *)img->getBasePtr(0, clippingArea.top - posY)); + posY = clippingArea.top; + } + + if (posX < clippingArea.left) { + img->w = MAX(0, (int)img->w - (clippingArea.left - posX)); + img->setPixels((byte *)img->getBasePtr(clippingArea.left - posX, 0)); + posX = clippingArea.left; + } + + img->w = CLIP((int)img->w, 0, (int)MAX((int)clippingArea.right - posX, 0)); + img->h = CLIP((int)img->h, 0, (int)MAX((int)clippingArea.bottom - posY, 0)); + + if ((img->w > 0) && (img->h > 0)) { + int xp = 0, yp = 0; + + int inStep = 4; + int inoStep = img->pitch; + if (flipping & FLIP_H) { + inStep = -inStep; + xp = img->w - 1; + } + + if (flipping & FLIP_V) { + inoStep = -inoStep; + yp = img->h - 1; + } + + byte *ino = (byte *)img->getBasePtr(xp, yp); + byte *outo = (byte *)target.getBasePtr(posX, posY); + + if (color == 0xFFFFFFFF && blendMode == BLEND_NORMAL && _alphaMode == ALPHA_OPAQUE) { + doBlitOpaqueFast(ino, outo, img->w, img->h, target.pitch, inStep, inoStep); + } else if (color == 0xFFFFFFFF && blendMode == BLEND_NORMAL && _alphaMode == ALPHA_BINARY) { + doBlitBinaryFast(ino, outo, img->w, img->h, target.pitch, inStep, inoStep); + } else { + if (blendMode == BLEND_ADDITIVE) { + doBlitAdditiveBlend(ino, outo, img->w, img->h, target.pitch, inStep, inoStep, color); + } else if (blendMode == BLEND_SUBTRACTIVE) { + doBlitSubtractiveBlend(ino, outo, img->w, img->h, target.pitch, inStep, inoStep, color); + } else { + assert(blendMode == BLEND_NORMAL); + doBlitAlphaBlend(ino, outo, img->w, img->h, target.pitch, inStep, inoStep, color); + } + } + + } + + retSize.setWidth(img->w); + retSize.setHeight(img->h); + + if (imgScaled) { + imgScaled->setPixels(savedPixels); + imgScaled->free(); + delete imgScaled; + } + + return retSize; +} + /** * Writes a color key to the alpha channel of the surface * @param rKey the red component of the color key @@ -848,4 +979,82 @@ TransparentSurface *TransparentSurface::scale(uint16 newWidth, uint16 newHeight) } +TransparentSurface *TransparentSurface::convertTo(const PixelFormat &dstFormat, const byte *palette) const { + assert(pixels); + + TransparentSurface *surface = new TransparentSurface(); + + // If the target format is the same, just copy + if (format == dstFormat) { + surface->copyFrom(*this); + return surface; + } + + if (format.bytesPerPixel == 0 || format.bytesPerPixel > 4) + error("Surface::convertTo(): Can only convert from 1Bpp, 2Bpp, 3Bpp, and 4Bpp"); + + if (dstFormat.bytesPerPixel != 2 && dstFormat.bytesPerPixel != 4) + error("Surface::convertTo(): Can only convert to 2Bpp and 4Bpp"); + + surface->create(w, h, dstFormat); + + if (format.bytesPerPixel == 1) { + // Converting from paletted to high color + assert(palette); + + for (int y = 0; y < h; y++) { + const byte *srcRow = (const byte *)getBasePtr(0, y); + byte *dstRow = (byte *)surface->getBasePtr(0, y); + + for (int x = 0; x < w; x++) { + byte index = *srcRow++; + byte r = palette[index * 3]; + byte g = palette[index * 3 + 1]; + byte b = palette[index * 3 + 2]; + + uint32 color = dstFormat.RGBToColor(r, g, b); + + if (dstFormat.bytesPerPixel == 2) + *((uint16 *)dstRow) = color; + else + *((uint32 *)dstRow) = color; + + dstRow += dstFormat.bytesPerPixel; + } + } + } else { + // Converting from high color to high color + for (int y = 0; y < h; y++) { + const byte *srcRow = (const byte *)getBasePtr(0, y); + byte *dstRow = (byte *)surface->getBasePtr(0, y); + + for (int x = 0; x < w; x++) { + uint32 srcColor; + if (format.bytesPerPixel == 2) + srcColor = READ_UINT16(srcRow); + else if (format.bytesPerPixel == 3) + srcColor = READ_UINT24(srcRow); + else + srcColor = READ_UINT32(srcRow); + + srcRow += format.bytesPerPixel; + + // Convert that color to the new format + byte r, g, b, a; + format.colorToARGB(srcColor, a, r, g, b); + uint32 color = dstFormat.ARGBToColor(a, r, g, b); + + if (dstFormat.bytesPerPixel == 2) + *((uint16 *)dstRow) = color; + else + *((uint32 *)dstRow) = color; + + dstRow += dstFormat.bytesPerPixel; + } + } + } + + return surface; +} + } // End of namespace Graphics diff --git a/graphics/transparent_surface.h b/graphics/transparent_surface.h index efb28149a5..8654183548 100644 --- a/graphics/transparent_surface.h +++ b/graphics/transparent_surface.h @@ -50,22 +50,22 @@ namespace Graphics { @brief The possible flipping parameters for the blit method. */ enum FLIP_FLAGS { - /// The image will not be flipped. - FLIP_NONE = 0, - /// The image will be flipped at the horizontal axis. - FLIP_H = 1, - /// The image will be flipped at the vertical axis. - FLIP_V = 2, - /// The image will be flipped at the horizontal and vertical axis. - FLIP_HV = FLIP_H | FLIP_V, - /// The image will be flipped at the horizontal and vertical axis. - FLIP_VH = FLIP_H | FLIP_V + /// The image will not be flipped. + FLIP_NONE = 0, + /// The image will be flipped at the horizontal axis. + FLIP_H = 1, + /// The image will be flipped at the vertical axis. + FLIP_V = 2, + /// The image will be flipped at the horizontal and vertical axis. + FLIP_HV = FLIP_H | FLIP_V, + /// The image will be flipped at the horizontal and vertical axis. + FLIP_VH = FLIP_H | FLIP_V }; enum AlphaType { - ALPHA_OPAQUE = 0, - ALPHA_BINARY = 1, - ALPHA_FULL = 2 + ALPHA_OPAQUE = 0, + ALPHA_BINARY = 1, + ALPHA_FULL = 2 }; /** @@ -75,6 +75,18 @@ struct TransparentSurface : public Graphics::Surface { TransparentSurface(); TransparentSurface(const Graphics::Surface &surf, bool copyData = false); + /** + * Returns the pixel format all operations of TransparentSurface support. + * + * Unlike Surface TransparentSurface only works with a fixed pixel format. + * This format can be queried using this static function. + * + * @return Supported pixel format. + */ + static PixelFormat getSupportedPixelFormat() { + return PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0); + } + void setColorKey(char r, char g, char b); void disableColorKey(); @@ -111,6 +123,14 @@ struct TransparentSurface : public Graphics::Surface { uint color = TS_ARGB(255, 255, 255, 255), int width = -1, int height = -1, TSpriteBlendMode blend = BLEND_NORMAL); + Common::Rect blitClip(Graphics::Surface &target, Common::Rect clippingArea, + int posX = 0, int posY = 0, + int flipping = FLIP_NONE, + Common::Rect *pPartRect = nullptr, + uint color = TS_ARGB(255, 255, 255, 255), + int width = -1, int height = -1, + TSpriteBlendMode blend = BLEND_NORMAL); + void applyColorKey(uint8 r, uint8 g, uint8 b, bool overwriteAlpha = false); /** @@ -131,6 +151,16 @@ struct TransparentSurface : public Graphics::Surface { * */ TransparentSurface *rotoscale(const TransformStruct &transform) const; + + TransparentSurface *convertTo(const PixelFormat &dstFormat, const byte *palette = 0) const; + + float getRatio() { + if (!w) + return 0; + + return h / (float)w; + } + AlphaType getAlphaMode() const; void setAlphaMode(AlphaType); private: |