diff options
Diffstat (limited to 'graphics')
37 files changed, 7088 insertions, 220 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 26f0ced625..3dd9c86f45 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); } /**************************** @@ -480,6 +644,49 @@ gradientFill(PixelType *ptr, int width, int x, int y) { 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; + + // 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++) { + if (realX + j - x < _clippingArea.left || realX + j - x >= _clippingArea.right) continue; + 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>:: fillSurface() { byte *ptr = (byte *)_activeSurface->getPixels(); @@ -503,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( @@ -554,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; @@ -590,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(); @@ -667,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 @@ -710,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 * @@ -818,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>:: @@ -857,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>:: @@ -894,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>:: @@ -920,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) @@ -956,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) @@ -1022,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; +} /******************************************************************** @@ -1134,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>:: @@ -1196,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>:: @@ -1240,6 +2220,57 @@ 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>:: @@ -1278,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>:: @@ -1334,6 +2405,72 @@ 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>:: @@ -1382,6 +2519,59 @@ drawLineAlg(int x1, int y1, int x2, int y2, uint dx, uint 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 @@ -1580,6 +2770,212 @@ 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>:: @@ -1682,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))); @@ -1709,6 +3108,74 @@ 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) { @@ -1740,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); @@ -1781,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; @@ -1810,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>:: @@ -1854,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); + } + } +} /******************************************************************** @@ -1905,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; @@ -1942,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); @@ -1978,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; + } +} /******************************************************************************/ @@ -2256,6 +4006,9 @@ 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; @@ -2272,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 3e54608b8e..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 @@ -152,40 +180,74 @@ protected: virtual void drawLineAlg(int x1, int y1, int x2, int y2, 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; diff --git a/graphics/font.cpp b/graphics/font.cpp index dba48249bc..7768b7362d 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" @@ -163,7 +164,16 @@ int wordWrapTextImpl(const Font &font, const StringType &str, int maxWidth, Comm typename StringType::unsigned_type last = 0; for (typename StringType::const_iterator x = str.begin(); x != str.end(); ++x) { - const typename StringType::unsigned_type c = *x; + typename StringType::unsigned_type c = *x; + + // Convert Windows and Mac line breaks into plain \n + if (c == '\r') { + if (x != str.end() && *(x + 1) == '\n') { + ++x; + } + c = '\n'; + } + const int w = font.getCharWidth(c) + font.getKerningOffset(last, c); last = c; const bool wouldExceedWidth = (lineWidth + tmpWidth + w > maxWidth); @@ -264,6 +274,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 +291,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/bdf.cpp b/graphics/fonts/bdf.cpp index 3476838911..511ea5cde1 100644 --- a/graphics/fonts/bdf.cpp +++ b/graphics/fonts/bdf.cpp @@ -41,13 +41,27 @@ BdfFont::~BdfFont() { delete[] _data.bitmaps; delete[] _data.advances; delete[] _data.boxes; + delete[] _data.familyName; + delete[] _data.slant; } } +const char *BdfFont::getFamilyName() const { + return _data.familyName; +} + +const char *BdfFont::getFontSlant() const { + return _data.slant; +} + int BdfFont::getFontHeight() const { return _data.height; } +int BdfFont::getFontSize() const { + return _data.size; +} + int BdfFont::getMaxCharWidth() const { return _data.maxAdvance; } @@ -102,7 +116,7 @@ void BdfFont::drawChar(Surface *dst, uint32 chr, const int tx, const int ty, con // TODO: Where is the relation between the max advance being smaller or // equal to 50 and the decision of the theme designer? // asserting _data.maxAdvance <= 50: let the theme designer decide what looks best - assert(_data.maxAdvance <= 50); + //assert(_data.maxAdvance <= 50); assert(dst->format.bytesPerPixel == 1 || dst->format.bytesPerPixel == 2 || dst->format.bytesPerPixel == 4); const int idx = mapToIndex(chr); @@ -285,6 +299,7 @@ BdfFont *BdfFont::loadFont(Common::SeekableReadStream &stream) { memset(bitmaps, 0, sizeof(byte *) * font.numCharacters); byte *advances = new byte[font.numCharacters]; BdfBoundingBox *boxes = new BdfBoundingBox[font.numCharacters]; + char *familyName, *slant; int descent = -1; @@ -297,6 +312,8 @@ BdfFont *BdfFont::loadFont(Common::SeekableReadStream &stream) { delete[] bitmaps; delete[] advances; delete[] boxes; + delete[] familyName; + delete[] slant; return 0; } @@ -310,6 +327,8 @@ BdfFont *BdfFont::loadFont(Common::SeekableReadStream &stream) { delete[] bitmaps; delete[] advances; delete[] boxes; + delete[] familyName; + delete[] slant; return 0; } @@ -317,6 +336,17 @@ BdfFont *BdfFont::loadFont(Common::SeekableReadStream &stream) { font.defaultBox.height = height; font.defaultBox.xOffset = xOffset; font.defaultBox.yOffset = yOffset; + } else if (line.hasPrefix("PIXEL_SIZE ")) { + if (sscanf(line.c_str(), "PIXEL_SIZE %d", &font.size) != 1) { + warning("BdfFont::loadFont: Invalid PIXEL_SIZE"); + freeBitmaps(bitmaps, font.numCharacters); + delete[] bitmaps; + delete[] advances; + delete[] boxes; + delete[] familyName; + delete[] slant; + return 0; + } } else if (line.hasPrefix("FONT_ASCENT ")) { if (sscanf(line.c_str(), "FONT_ASCENT %d", &font.ascent) != 1) { warning("BdfFont::loadFont: Invalid FONT_ASCENT"); @@ -324,6 +354,8 @@ BdfFont *BdfFont::loadFont(Common::SeekableReadStream &stream) { delete[] bitmaps; delete[] advances; delete[] boxes; + delete[] familyName; + delete[] slant; return 0; } } else if (line.hasPrefix("FONT_DESCENT ")) { @@ -333,6 +365,8 @@ BdfFont *BdfFont::loadFont(Common::SeekableReadStream &stream) { delete[] bitmaps; delete[] advances; delete[] boxes; + delete[] familyName; + delete[] slant; return 0; } } else if (line.hasPrefix("DEFAULT_CHAR ")) { @@ -342,6 +376,8 @@ BdfFont *BdfFont::loadFont(Common::SeekableReadStream &stream) { delete[] bitmaps; delete[] advances; delete[] boxes; + delete[] familyName; + delete[] slant; return 0; } } else if (line.hasPrefix("STARTCHAR ")) { @@ -366,6 +402,8 @@ BdfFont *BdfFont::loadFont(Common::SeekableReadStream &stream) { delete[] bitmaps; delete[] advances; delete[] boxes; + delete[] familyName; + delete[] slant; return 0; } @@ -374,6 +412,40 @@ BdfFont *BdfFont::loadFont(Common::SeekableReadStream &stream) { advances[encoding] = advance; boxes[encoding] = box; } + } else if (line.hasPrefix("FAMILY_NAME \"")) { + familyName = new char[line.size()]; // We will definitely fit here + Common::strlcpy(familyName, &line.c_str()[13], line.size()); + char *p = &familyName[strlen(familyName)]; + while (p != familyName && *p != '"') + p--; + if (p == familyName) { + warning("BdfFont::loadFont: Invalid FAMILY_NAME"); + freeBitmaps(bitmaps, font.numCharacters); + delete[] bitmaps; + delete[] advances; + delete[] boxes; + delete[] familyName; + delete[] slant; + return 0; + } + *p = '\0'; // Remove last quote + } else if (line.hasPrefix("SLANT \"")) { + slant = new char[line.size()]; // We will definitely fit here + Common::strlcpy(slant, &line.c_str()[7], line.size()); + char *p = &slant[strlen(slant)]; + while (p != slant && *p != '"') + p--; + if (p == slant) { + warning("BdfFont::loadFont: Invalid SLANT"); + freeBitmaps(bitmaps, font.numCharacters); + delete[] bitmaps; + delete[] advances; + delete[] boxes; + delete[] familyName; + delete[] slant; + return 0; + } + *p = '\0'; // Remove last quote } else if (line == "ENDFONT") { break; } @@ -385,6 +457,8 @@ BdfFont *BdfFont::loadFont(Common::SeekableReadStream &stream) { delete[] bitmaps; delete[] advances; delete[] boxes; + delete[] familyName; + delete[] slant; return 0; } @@ -393,6 +467,8 @@ BdfFont *BdfFont::loadFont(Common::SeekableReadStream &stream) { font.bitmaps = bitmaps; font.advances = advances; font.boxes = boxes; + font.familyName = familyName; + font.slant = slant; int firstCharacter = font.numCharacters; int lastCharacter = -1; @@ -425,6 +501,8 @@ BdfFont *BdfFont::loadFont(Common::SeekableReadStream &stream) { delete[] font.bitmaps; delete[] font.advances; delete[] font.boxes; + delete[] familyName; + delete[] slant; return 0; } @@ -622,4 +700,99 @@ BdfFont *BdfFont::loadFromCache(Common::SeekableReadStream &stream) { return new BdfFont(data, DisposeAfterUse::YES); } +BdfFont *BdfFont::scaleFont(BdfFont *src, int newSize) { + if (!src) { + warning("Empty font reference in scale font"); + return NULL; + } + + if (src->getFontSize() == 0) { + warning("Requested to scale 0 size font"); + return NULL; + } + + float scale = (float)newSize / (float)src->getFontSize(); + + BdfFontData data; + + data.maxAdvance = (int)((float)src->_data.maxAdvance * scale); + data.height = (int)((float)src->_data.height * scale); + data.defaultBox.width = (int)((float)src->_data.defaultBox.width * scale); + data.defaultBox.height = (int)((float)src->_data.defaultBox.height * scale); + data.defaultBox.xOffset = (int)((float)src->_data.defaultBox.xOffset * scale); + data.defaultBox.yOffset = (int)((float)src->_data.defaultBox.yOffset * scale); + data.ascent = (int)((float)src->_data.ascent * scale); + data.firstCharacter = src->_data.firstCharacter; + data.defaultCharacter = src->_data.defaultCharacter; + data.numCharacters = src->_data.numCharacters; + data.familyName = strdup(src->_data.familyName); + data.slant = strdup(src->_data.slant); + + BdfBoundingBox *boxes = new BdfBoundingBox[data.numCharacters]; + for (int i = 0; i < data.numCharacters; ++i) { + boxes[i].width = (int)((float)src->_data.boxes[i].width * scale); + boxes[i].height = (int)((float)src->_data.boxes[i].height * scale); + boxes[i].xOffset = (int)((float)src->_data.boxes[i].xOffset * scale); + boxes[i].yOffset = (int)((float)src->_data.boxes[i].yOffset * scale); + } + data.boxes = boxes; + + byte *advances = new byte[data.numCharacters]; + for (int i = 0; i < data.numCharacters; ++i) { + advances[i] = (int)((float)src->_data.advances[i] * scale); + } + data.advances = advances; + + byte **bitmaps = new byte *[data.numCharacters]; + + for (int i = 0; i < data.numCharacters; i++) { + const BdfBoundingBox &box = data.boxes ? data.boxes[i] : data.defaultBox; + const BdfBoundingBox &srcBox = data.boxes ? src->_data.boxes[i] : src->_data.defaultBox; + + if (src->_data.bitmaps[i]) { + const int bytes = ((box.width + 7) / 8) * box.height; // Dimensions have been already corrected + bitmaps[i] = new byte[bytes]; + + int srcPitch = (srcBox.width + 7) / 8; + int dstPitch = (box.width + 7) / 8; + + byte *ptr = bitmaps[i]; + + for (int y = 0; y < box.height; y++) { + const byte *srcd = (const byte *)&src->_data.bitmaps[i][((int)((float)y / scale)) * srcPitch]; + byte *dst = ptr; + byte b = 0; + + for (int x = 0; x < box.width; x++) { + int sx = (int)((float)x / scale); + + if (srcd[sx / 8] & (0x80 >> (sx % 8))) + b |= 1; + + if (!(x % 8) && x) { + *dst++ = b; + b = 0; + } + + b <<= 1; + } + + if (((box.width - 1) % 8)) { + b <<= 7 - ((box.width - 1) % 8); + *dst = b; + } + + ptr += dstPitch; + } + + } else { + bitmaps[i] = 0; + } + } + + data.bitmaps = bitmaps; + + return new BdfFont(data, DisposeAfterUse::YES); +} + } // End of namespace Graphics diff --git a/graphics/fonts/bdf.h b/graphics/fonts/bdf.h index b91834785f..d99b5fd401 100644 --- a/graphics/fonts/bdf.h +++ b/graphics/fonts/bdf.h @@ -40,8 +40,12 @@ struct BdfBoundingBox { }; struct BdfFontData { + const char *familyName; + const char *slant; + int maxAdvance; int height; + int size; BdfBoundingBox defaultBox; int ascent; @@ -65,9 +69,14 @@ public: virtual int getCharWidth(uint32 chr) const; virtual void drawChar(Surface *dst, uint32 chr, int x, int y, uint32 color) const; + const char *getFamilyName() const; + const char *getFontSlant() const; + int getFontSize() const; + static BdfFont *loadFont(Common::SeekableReadStream &stream); static bool cacheFontData(const BdfFont &font, const Common::String &filename); static BdfFont *loadFromCache(Common::SeekableReadStream &stream); + static BdfFont *scaleFont(BdfFont *src, int newSize); private: int mapToIndex(uint32 ch) const; diff --git a/graphics/fonts/consolefont.cpp b/graphics/fonts/consolefont.cpp index 748aa08a5c..6a84cdeaee 100644 --- a/graphics/fonts/consolefont.cpp +++ b/graphics/fonts/consolefont.cpp @@ -5850,8 +5850,11 @@ const byte *const bitmapTable[] = { // Font structure static const BdfFontData desc = { + "Fixed", // Family name + "R", // Slant 5, // Max advance 8, // Height + 8, // Size { 5, 8, 0, -1 }, // Bounding box 7, // Ascent diff --git a/graphics/fonts/newfont.cpp b/graphics/fonts/newfont.cpp index 4922e24676..8d1ce8b905 100644 --- a/graphics/fonts/newfont.cpp +++ b/graphics/fonts/newfont.cpp @@ -7634,8 +7634,11 @@ const byte *const bitmapTable[] = { // Font structure static const BdfFontData desc = { + "Schumacher", // Family name + "R", // Slant 6, // Max advance 12, // Height + 12, // Size { 6, 12, 0, -3 }, // Bounding box 9, // Ascent diff --git a/graphics/fonts/newfont_big.cpp b/graphics/fonts/newfont_big.cpp index 550d6dbfa9..28a1162434 100644 --- a/graphics/fonts/newfont_big.cpp +++ b/graphics/fonts/newfont_big.cpp @@ -5829,8 +5829,11 @@ static const BdfBoundingBox boxes[] = { // Font structure static const BdfFontData desc = { + "Helvetica", // Family name + "R", // Slant 13, // Max advance 14, // Height + 12, // Size { 13, 15, -1, -3 }, // Bounding box 11, // Ascent diff --git a/graphics/macgui/macfontmanager.cpp b/graphics/macgui/macfontmanager.cpp new file mode 100644 index 0000000000..21f9c08160 --- /dev/null +++ b/graphics/macgui/macfontmanager.cpp @@ -0,0 +1,282 @@ +/* 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/archive.h" +#include "common/stream.h" +#include "common/unzip.h" +#include "graphics/fonts/bdf.h" + +#include "graphics/macgui/macfontmanager.h" + +namespace Graphics { + +// 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" +}; + +MacFontManager::MacFontManager() { + for (uint i = 0; i < ARRAYSIZE(fontNames); i++) + if (fontNames[i]) + _fontNames.setVal(fontNames[i], i); + + loadFonts(); +} + +void MacFontManager::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; + MacFont *macfont; + + if (font->getFamilyName() && *font->getFamilyName()) { + fontName = Common::String::format("%s-%s-%d", font->getFamilyName(), font->getFontSlant(), font->getFontSize()); + + macfont = new MacFont(_fontNames.getVal(font->getFamilyName(), kMacFontNonStandard), font->getFontSize(), parseFontSlant(font->getFontSlant())); + } else { // Get it from the file name + 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; + } + } + + macfont = new MacFont(kMacFontNonStandard); + macfont->setName(fontName); + } + + FontMan.assignFontToName(fontName, font); + macfont->setBdfFont(font); + _fontRegistry.setVal(fontName, macfont); + + debug(2, " %s", fontName.c_str()); + } + + _builtInFonts = false; + + delete dat; +} + +const Font *MacFontManager::getFont(MacFont macFont) { + const Font *font = 0; + + if (!_builtInFonts) { + if (macFont.getName().empty()) + macFont.setName(getFontName(macFont.getId(), macFont.getSize(), macFont.getSlant())); + + if (!_fontRegistry.contains(macFont.getName())) + generateFontSubstitute(macFont); + + font = FontMan.getFontByName(macFont.getName()); + + if (!font) { + warning("Cannot load font '%s'", macFont.getName().c_str()); + + font = FontMan.getFontByName(MacFont(kMacFontChicago, 12).getName()); + } + } + + if (_builtInFonts || !font) + font = FontMan.getFontByUsage(macFont.getFallback()); + + return font; +} + +int MacFontManager::parseFontSlant(Common::String slant) { + slant.toUppercase(); + + if (slant == "I") + return kMacFontItalic; + if (slant == "B") + return kMacFontBold; + if (slant == "R") + return kMacFontRegular; + + warning("Unknown font slant '%s'", slant.c_str()); + + return kMacFontRegular; +} + +const char *MacFontManager::getFontName(int id, int size, int slant) { + static char name[128]; + const char *sslant; + + switch (slant) { + case kMacFontItalic: + sslant = "I"; + break; + case kMacFontBold: + sslant = "B"; + break; + case kMacFontRegular: + default: + sslant = "R"; + break; + } + + if (id > ARRAYSIZE(fontNames)) + return NULL; + + snprintf(name, 128, "%s-%s-%d", fontNames[id], sslant, size); + + return name; +} + +const char *MacFontManager::getFontName(MacFont &font) { + return getFontName(font.getId(), font.getSize(), font.getSlant()); +} + +void MacFontManager::generateFontSubstitute(MacFont &macFont) { + Common::String name; + + // First we try twice size + name = getFontName(macFont.getId(), macFont.getSize() * 2, macFont.getSlant()); + if (_fontRegistry.contains(name) && !_fontRegistry[name]->isGenerated()) { + generateFont(macFont, *_fontRegistry[name]); + + return; + } + + // Now half size + name = getFontName(macFont.getId(), macFont.getSize() / 2, macFont.getSlant()); + if (_fontRegistry.contains(name) && !_fontRegistry[name]->isGenerated()) { + generateFont(macFont, *_fontRegistry[name]); + + return; + } + + // No simple substitute was found. Looking for neighborhood fonts + + // First we gather all font sizes for this font + Common::Array<int> sizes; + for (Common::HashMap<Common::String, MacFont *>::iterator i = _fontRegistry.begin(); i != _fontRegistry.end(); ++i) { + if (i->_value->getId() == macFont.getId() && i->_value->getSlant() == macFont.getSlant() && !i->_value->isGenerated()) + sizes.push_back(i->_value->getSize()); + } + + if (sizes.empty()) { + warning("No viable substitute found for font %s", getFontName(macFont)); + return; + } + + // Now looking next larger font, and store the largest one for next check + int candidate = 1000; + int maxSize = sizes[0]; + for (uint i = 0; i < sizes.size(); i++) { + if (sizes[i] > macFont.getSize() && sizes[i] < candidate) + candidate = sizes[i]; + + if (sizes[i] > maxSize) + maxSize = sizes[i]; + } + + if (candidate != 1000) { + generateFont(macFont, *_fontRegistry[getFontName(macFont.getId(), candidate, macFont.getSlant())]); + return; + } + + // Now next smaller font, which is the biggest we have + generateFont(macFont, *_fontRegistry[getFontName(macFont.getId(), maxSize, macFont.getSlant())]); +} + +void MacFontManager::generateFont(MacFont &toFont, MacFont &fromFont) { + debugN("Found font substitute for font '%s' ", getFontName(toFont)); + debug("as '%s'", getFontName(fromFont)); + + Graphics::BdfFont *font = Graphics::BdfFont::scaleFont(fromFont.getBdfFont(), toFont.getSize()); + + if (!font) { + warning("Failed to generate font '%s'", getFontName(toFont)); + } + + toFont.setGenerated(true); + toFont.setBdfFont(font); + + FontMan.assignFontToName(getFontName(toFont), font); + _fontRegistry.setVal(getFontName(toFont), new MacFont(toFont)); + + debug("Generated font '%s'", getFontName(toFont)); +} + +} // End of namespace Graphics diff --git a/graphics/macgui/macfontmanager.h b/graphics/macgui/macfontmanager.h new file mode 100644 index 0000000000..09fd0aeaca --- /dev/null +++ b/graphics/macgui/macfontmanager.h @@ -0,0 +1,134 @@ +/* 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_MACGUI_MACFONTMANAGER_H +#define GRAPHICS_MACGUI_MACFONTMANAGER_H + +#include "graphics/fontman.h" + +namespace Graphics { + +enum { + kMacFontNonStandard = -1, + kMacFontChicago = 0, + kMacFontGeneva = 1, + kMacFontNewYork = 2, + kMacFontMonaco = 4, + kMacFontVenice = 5, + kMacFontLondon = 6, + kMacFontAthens = 7, + kMacFontSanFrancisco = 8, + kMacFontCairo = 11, + kMacFontLosAngeles = 12, + kMacFontPalatino = 16, + kMacFontTimes = 20, + kMacFontHelvetica = 21, + kMacFontCourier = 22, + kMacFontSymbol = 23 +}; + +enum { + kMacFontRegular, + kMacFontBold, + kMacFontItalic +}; + +class BdfFont; + +class MacFont { +public: + MacFont(int id = kMacFontChicago, int size = 12, int slant = kMacFontRegular, FontManager::FontUsage fallback = Graphics::FontManager::kBigGUIFont) { + _id = id; + _size = size; + _slant = slant; + _fallback = fallback; + _generated = false; + _bdfFont = NULL; + } + + int getId() { return _id; }; + int getSize() { return _size; } + int getSlant() { return _slant; } + Common::String getName() { return _name; } + void setName(Common::String &name) { _name = name; } + void setName(const char *name) { _name = name; } + FontManager::FontUsage getFallback() { return _fallback; } + bool isGenerated() { return _generated; } + void setGenerated(bool gen) { _generated = gen; } + BdfFont *getBdfFont() { return _bdfFont; } + void setBdfFont(BdfFont *bdfFont) { _bdfFont = bdfFont; } + +private: + int _id; + int _size; + int _slant; + Common::String _name; + FontManager::FontUsage _fallback; + + bool _generated; + BdfFont *_bdfFont; +}; + +class MacFontManager { +public: + MacFontManager(); + + /** + * 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(MacFont macFont); + + /** + * 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, int slant = kMacFontRegular); + const char *getFontName(MacFont &font); + +private: + void loadFonts(); + + void generateFontSubstitute(MacFont &macFont); + void generateFont(MacFont &toFont, MacFont &fromFont); + +private: + bool _builtInFonts; + Common::HashMap<Common::String, MacFont *> _fontRegistry; + + Common::HashMap<Common::String, int> _fontNames; + + int parseFontSlant(Common::String slant); +}; + +} // End of namespace Graphics + +#endif diff --git a/graphics/macgui/macmenu.cpp b/graphics/macgui/macmenu.cpp new file mode 100644 index 0000000000..449cf58335 --- /dev/null +++ b/graphics/macgui/macmenu.cpp @@ -0,0 +1,559 @@ +/* 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/keyboard.h" + +#include "graphics/primitives.h" +#include "graphics/font.h" +#include "graphics/macgui/macfontmanager.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->_fontMan->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->_fontMan->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->_fontMan->getFont(Graphics::MacFont(kMacFontChicago, 12)); +} + +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->_fontMan->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->_fontMan->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->_fontMan->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..8e02ee1403 --- /dev/null +++ b/graphics/macgui/macmenu.h @@ -0,0 +1,98 @@ +/* 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_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/mactext.cpp b/graphics/macgui/mactext.cpp new file mode 100644 index 0000000000..06354457f2 --- /dev/null +++ b/graphics/macgui/mactext.cpp @@ -0,0 +1,158 @@ +/* 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/macgui/mactext.h" +#include "graphics/font.h" + +namespace Graphics { + +MacText::MacText(Common::String s, Graphics::Font *font, int fgcolor, int bgcolor, int maxWidth) { + _str = s; + _font = font; + _fgcolor = fgcolor; + _bgcolor = bgcolor; + _maxWidth = maxWidth; + _surface = nullptr; + + _interLinear = 0; // 0 pixels between the lines by default + + if (_maxWidth == -1) + _textMaxWidth = 1000000; // Some big value + else + _textMaxWidth = -1; + + splitString(_str); + + _fullRefresh = true; +} + +void MacText::splitString(Common::String &str) { + const char *s = str.c_str(); + + Common::String tmp; + bool prevCR = false; + + while (*s) { + if (*s == '\n' && prevCR) { // trean \r\n as one + prevCR = false; + continue; + } + + if (*s == '\r') + prevCR = true; + + if (*s == '\r' || *s == '\n') { + _maxWidth = MIN(_font->wordWrapText(tmp, _maxWidth, _text), _maxWidth); + + tmp.clear(); + + continue; + } + + tmp += *s; + } + + if (tmp.size()) + _maxWidth = MIN(_font->wordWrapText(tmp, _maxWidth, _text), _maxWidth); +} + +void MacText::reallocSurface() { + int lineH = _font->getFontHeight() + _interLinear; + // round to closest 10 + int requiredH = (_text.size() + (_text.size() * 10 + 9) / 10) * lineH; + int surfW = _maxWidth == -1 ? _textMaxWidth : _maxWidth; + + if (!_surface) { + _surface = new ManagedSurface(surfW, requiredH); + + return; + } + + if (_surface->h < requiredH) { + // realloc surface and copy old content + ManagedSurface *n = new ManagedSurface(surfW, requiredH); + n->blitFrom(*_surface, Common::Point(0, 0)); + + delete _surface; + _surface = n; + } +} + +void MacText::render() { + if (_fullRefresh) { + render(0, _text.size()); + + _fullRefresh = false; + } +} + +void MacText::render(int from, int to) { + reallocSurface(); + + from = MAX<int>(0, from); + to = MIN<int>(to, _text.size()); + + int lineH = _font->getFontHeight() + _interLinear; + int y = from * lineH; + + // Clear the screen + _surface->fillRect(Common::Rect(0, y, _surface->w, to * lineH), _bgcolor); + + for (uint i = from; i < to; i++) { + _font->drawString(_surface, _text[i], 0, y, _textMaxWidth, _fgcolor); + + y += _font->getFontHeight() + _interLinear; + } + +} + +void MacText::draw(ManagedSurface *g, int x, int y, int w, int h, int xoff, int yoff) { + render(); + + if (x + w < _surface->w || y + h < _surface->h) { + g->fillRect(Common::Rect(x, y, x + w, y + w), _bgcolor); + } + + g->blitFrom(*_surface, Common::Rect(MIN<int>(_surface->w, x), MIN<int>(_surface->h, y), + MIN<int>(_surface->w, x + w), MIN<int>(_surface->w, y + w)), + Common::Point(xoff, yoff)); +} + +void MacText::appendText(Common::String str) { + int oldLen = _text.size(); + + splitString(str); + + render(oldLen + 1, _text.size()); +} + +void MacText::replaceLastLine(Common::String str) { + int oldLen = MAX<int>(0, _text.size() - 1); + + if (_text.size()) + _text.pop_back(); + + splitString(str); + + render(oldLen, _text.size()); +} + +} // End of namespace Graphics diff --git a/graphics/macgui/mactext.h b/graphics/macgui/mactext.h new file mode 100644 index 0000000000..e035eb123f --- /dev/null +++ b/graphics/macgui/mactext.h @@ -0,0 +1,66 @@ +/* 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_MACGUI_MACTEXT_H +#define GRAPHICS_MACGUI_MACTEXT_H + +#include "graphics/fontman.h" +#include "graphics/managed_surface.h" + +namespace Graphics { + +class MacText { +public: + MacText(Common::String s, Graphics::Font *font, int fgcolor, int bgcolor, int maxWidth = -1); + + void setInterLinear(int interLinear) { _interLinear = interLinear; } + + void draw(ManagedSurface *g, int x, int y, int w, int h, int xoff, int yoff); + void appendText(Common::String str); + void replaceLastLine(Common::String str); + +private: + void splitString(Common::String &s); + void render(); + void render(int from, int to); + void calcMaxWidth(); + void reallocSurface(); + +private: + Common::String _str; + Graphics::Font *_font; + int _fgcolor, _bgcolor; + + int _maxWidth; + int _interLinear; + + Common::Array<Common::String> _text; + + int _textMaxWidth; + + Graphics::ManagedSurface *_surface; + bool _fullRefresh; +}; + +} // End of namespace Graphics + +#endif diff --git a/graphics/macgui/macwindow.cpp b/graphics/macgui/macwindow.cpp new file mode 100644 index 0000000000..0fce19e482 --- /dev/null +++ b/graphics/macgui/macwindow.cpp @@ -0,0 +1,500 @@ +/* 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/font.h" +#include "graphics/primitives.h" +#include "common/events.h" +#include "graphics/macgui/macfontmanager.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; + + _pattern = 0; + _hasPattern = false; + + _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->_fontMan->getFont(Graphics::MacFont(kMacFontChicago, 12)); +} + +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()); + + if (_hasPattern) + drawPattern(); + + _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; +} + +void MacWindow::setBackgroundPattern(int pattern) { + _pattern = pattern; + _hasPattern = true; + drawPattern(); + _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->_fontMan->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::drawPattern() { + byte *pat = _wm->getPatterns()[_pattern - 1]; + for (int y = 0; y < _surface.h; y++) { + for (int x = 0; x < _surface.w; x++) { + byte *dst = (byte *)_surface.getBasePtr(x, y); + if (pat[y % 8] & (1 << (7 - (x % 8)))) + *dst = kColorBlack; + else + *dst = kColorWhite; + } + } +} + +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()->convertTo(surface->getSupportedPixelFormat(), bmpDecoder.getPalette()); + + surface->create(source->w, source->h, surface->getSupportedPixelFormat()); + 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(); + source->free(); + delete source; +} + +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..5446b65678 --- /dev/null +++ b/graphics/macgui/macwindow.h @@ -0,0 +1,337 @@ +/* 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_MACGUI_MACWINDOW_H +#define GRAPHICS_MACGUI_MACWINDOW_H + +#include "common/stream.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; } + + /** + * Set a background pattern for the window. + * @param pattern + */ + void setBackgroundPattern(int pattern); + + /** + * 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 drawPattern(); + 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; + + int _pattern; + bool _hasPattern; + + 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..a8efdd3a67 --- /dev/null +++ b/graphics/macgui/macwindowborder.cpp @@ -0,0 +1,93 @@ +/* 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 "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, true); + _activeInitialized = true; +} + +void MacWindowBorder::addInactiveBorder(TransparentSurface *source) { + assert(!_inactiveBorder); + _inactiveBorder = new NinePatchBitmap(source, true); + _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); + srf.free(); +} + +} // End of namespace Graphics diff --git a/graphics/macgui/macwindowborder.h b/graphics/macgui/macwindowborder.h new file mode 100644 index 0000000000..1500b9b847 --- /dev/null +++ b/graphics/macgui/macwindowborder.h @@ -0,0 +1,124 @@ +/* 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_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..42cab7cf8e --- /dev/null +++ b/graphics/macgui/macwindowmanager.cpp @@ -0,0 +1,335 @@ +/* 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/array.h" +#include "common/events.h" +#include "common/list.h" +#include "common/system.h" + +#include "graphics/cursorman.h" +#include "graphics/managed_surface.h" +#include "graphics/palette.h" +#include "graphics/primitives.h" +#include "graphics/macgui/macwindowmanager.h" +#include "graphics/macgui/macfontmanager.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 + { 0x88, 0x22, 0x88, 0x22, 0x88, 0x22, 0x88, 0x22 }, // kPatternLightGray + { 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd, 0x77, 0xdd } // kPatternDarkGray +}; + +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; + + for (int i = 0; i < ARRAYSIZE(fillPatterns); i++) + _patterns.push_back(fillPatterns[i]); + + g_system->getPaletteManager()->setPalette(palette, 0, ARRAYSIZE(palette) / 3); + + _fontMan = new MacFontManager(); + + 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]; + + delete _fontMan; +} + +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; + _lastId = _windows.size(); +} + +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); +} + +///////////////// +// 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..3449ab13c2 --- /dev/null +++ b/graphics/macgui/macwindowmanager.h @@ -0,0 +1,184 @@ +/* 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_MACGUI_MACWINDOWMANAGER_H +#define GRAPHICS_MACGUI_MACWINDOWMANAGER_H + +#include "common/array.h" +#include "common/list.h" +#include "common/events.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, + kPatternLightGray = 5, + kPatternDarkGray = 6 +}; +} +using namespace MacGUIConstants; + +class ManagedSurface; + +class Menu; + +class MacFontManager; + +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; } + /** + * 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(); + +public: + MacFontManager *_fontMan; + +private: + void drawDesktop(); + + 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 _cursorIsArrow; +}; + +} // End of namespace Graphics + +#endif diff --git a/graphics/managed_surface.cpp b/graphics/managed_surface.cpp new file mode 100644 index 0000000000..e781d7a27a --- /dev/null +++ b/graphics/managed_surface.cpp @@ -0,0 +1,341 @@ +/* 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()); + uint destPixel; + byte rSrc, gSrc, bSrc, aSrc; + byte rDest, gDest, bDest; + double alpha; + + if (!srcRect.isValidRect() || !clip(srcBounds, destBounds)) + return; + + if (format != src.format) { + // When the pixel format differs, both source an dest must be + // 2 or 4 bytes per pixel + assert(format.bytesPerPixel == 2 || format.bytesPerPixel == 4); + assert(src.format.bytesPerPixel == 2 || src.format.bytesPerPixel == 4); + } + + 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); + + if (src.format == format) { + // Matching surface formats, so we can do a straight copy + Common::copy(srcP, srcP + srcBounds.width() * format.bytesPerPixel, destP); + } else { + for (int x = 0; x < srcBounds.width(); ++x, + srcP += src.format.bytesPerPixel, + destP += format.bytesPerPixel) { + src.format.colorToARGB(src.format.bytesPerPixel == 2 ? *(const uint16 *)srcP : *(const uint32 *)srcP, + aSrc, rSrc, gSrc, bSrc); + format.colorToRGB(format.bytesPerPixel == 2 ? *(const uint16 *)destP : *(const uint32 *)destP, + rDest, gDest, bDest); + + if (aSrc == 0) { + // Completely transparent, so skip + continue; + } else if (aSrc == 0xff) { + // Completely opaque, so copy RGB values over + rDest = rSrc; + gDest = gSrc; + bDest = bSrc; + } else { + // Partially transparent, so calculate new pixel colors + alpha = (double)aSrc / 255.0; + rDest = (rSrc * alpha) + (rDest * (1.0 - alpha)); + gDest = (gSrc * alpha) + (gDest * (1.0 - alpha)); + bDest = (bSrc * alpha) + (bDest * (1.0 - alpha)); + } + + destPixel = format.ARGBToColor(0xff, rDest, gDest, bDest); + if (format.bytesPerPixel == 2) + *(uint16 *)destP = destPixel; + else + *(uint32 *)destP = destPixel; + } + } + } + + 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 TSRC, typename TDEST> +void transBlit(const Surface &src, const Common::Rect &srcRect, Surface &dest, const Common::Rect &destRect, TSRC transColor, bool flipped, uint overrideColor) { + int scaleX = SCALE_THRESHOLD * srcRect.width() / destRect.width(); + int scaleY = SCALE_THRESHOLD * srcRect.height() / destRect.height(); + const Graphics::PixelFormat &srcFormat = src.format; + const Graphics::PixelFormat &destFormat = dest.format; + byte aSrc, rSrc, gSrc, bSrc; + byte rDest, gDest, bDest; + double alpha; + + // 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 TSRC *srcLine = (const TSRC *)src.getBasePtr(0, scaleYCtr / SCALE_THRESHOLD); + TDEST *destLine = (TDEST *)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; + + TSRC srcVal = srcLine[flipped ? src.w - scaleXCtr / SCALE_THRESHOLD - 1 : scaleXCtr / SCALE_THRESHOLD]; + if (srcVal == transColor) + continue; + + if (srcFormat == destFormat) { + // Matching formats, so we can do a straight copy + destLine[xCtr] = overrideColor ? overrideColor : srcVal; + } else { + // Otherwise we have to manually decode and re-encode each pixel + srcFormat.colorToARGB(*srcLine, aSrc, rSrc, gSrc, bSrc); + destFormat.colorToRGB(destLine[xCtr], rDest, gDest, bDest); + + if (aSrc == 0) { + // Completely transparent, so skip + continue; + } else if (aSrc == 0xff) { + // Completely opaque, so copy RGB values over + rDest = rSrc; + gDest = gSrc; + bDest = bSrc; + } else { + // Partially transparent, so calculate new pixel colors + alpha = (double)aSrc / 255.0; + rDest = (rSrc * alpha) + (rDest * (1.0 - alpha)); + gDest = (gSrc * alpha) + (gDest * (1.0 - alpha)); + bDest = (bSrc * alpha) + (bDest * (1.0 - alpha)); + } + + destLine[xCtr] = destFormat.ARGBToColor(0xff, rDest, gDest, bDest); + } + } + } +} + +#define HANDLE_BLIT(SRC_BYTES, DEST_BYTES, SRC_TYPE, DEST_TYPE) \ + if (src.format.bytesPerPixel == SRC_BYTES && format.bytesPerPixel == DEST_BYTES) \ + transBlit<SRC_TYPE, DEST_TYPE>(src, srcRect, _innerSurface, destRect, transColor, flipped, overrideColor); \ + else + +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; + + HANDLE_BLIT(1, 1, byte, byte) + HANDLE_BLIT(2, 2, uint16, uint16) + HANDLE_BLIT(4, 4, uint32, uint32) + HANDLE_BLIT(2, 4, uint16, uint32) + HANDLE_BLIT(4, 2, uint32, uint16) + error("Surface::transBlitFrom: bytesPerPixel must be 1, 2, or 4"); + + // Mark the affected area + addDirtyRect(destRect); +} + +#undef HANDLE_BLIT + +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..422b4610be --- /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 b6919cf1ab..51e2ef7f2d 100644 --- a/graphics/module.mk +++ b/graphics/module.mk @@ -12,10 +12,19 @@ MODULE_OBJS := \ fonts/ttf.o \ fonts/winfont.o \ maccursor.o \ + macgui/macfontmanager.o \ + macgui/macmenu.o \ + macgui/mactext.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..56e1202be3 --- /dev/null +++ b/graphics/nine_patch.cpp @@ -0,0 +1,364 @@ +/* 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 = new Surface(); + 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); + } + } + } + + srf->free(); + delete srf; + + return; + } + + /* Else, draw regions normally */ + drawRegions(target, dx, dy, dw, dh); +} + +NinePatchBitmap::~NinePatchBitmap() { + if (_destroy_bmp) { + _bmp->free(); + 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.h b/graphics/pixelformat.h index 9dd06241b7..7b966a8a15 100644 --- a/graphics/pixelformat.h +++ b/graphics/pixelformat.h @@ -215,6 +215,10 @@ struct PixelFormat { return (8 - aLoss); } + inline byte bpp() const { + return rBits() + gBits() + bBits() + aBits(); + } + //////////////////////////////////////////////////////////////////////// // Convenience functions for getting color components' maximum values // //////////////////////////////////////////////////////////////////////// diff --git a/graphics/primitives.cpp b/graphics/primitives.cpp index ac1c58b1d8..8663a61606 100644 --- a/graphics/primitives.cpp +++ b/graphics/primitives.cpp @@ -108,15 +108,13 @@ void drawThickLine2(int x1, int y1, int x2, int y2, int thick, int color, void ( int dy = abs(y2 - y1); if (dx == 0) { - if (y1 > y2) - SWAP(y1, y2); - Common::Rect r(x1, y1, x1 + thick - 1, y2); + 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) { - if (x1 > x2) - SWAP(x1, x2); - Common::Rect r(x1, y1, x2, y1 + thick - 1); + int yn = y1 - thick / 2; + Common::Rect r(MIN(x1, x2), yn, MAX(x1, x2), yn + thick - 1); drawFilledRect(r, color, plotProc, data); return; } 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..a727936f5c --- /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/transparent_surface.cpp b/graphics/transparent_surface.cpp index 19e7655a93..2a5decf4e2 100644 --- a/graphics/transparent_surface.cpp +++ b/graphics/transparent_surface.cpp @@ -37,8 +37,6 @@ #include "graphics/transparent_surface.h" #include "graphics/transform_tools.h" -//#define ENABLE_BILINEAR - namespace Graphics { static const int kBModShift = 0;//img->format.bShift; @@ -462,6 +460,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 @@ -544,6 +675,7 @@ systems. +template <TFilteringMode filteringMode> TransparentSurface *TransparentSurface::rotoscale(const TransformStruct &transform) const { assert(transform._angle != 0); // This would not be ideal; rotoscale() should never be called in conditional branches where angle = 0 anyway. @@ -606,50 +738,50 @@ TransparentSurface *TransparentSurface::rotoscale(const TransformStruct &transfo dy = sh - dy; } -#ifdef ENABLE_BILINEAR - if ((dx > -1) && (dy > -1) && (dx < sw) && (dy < sh)) { - const tColorRGBA *sp = (const tColorRGBA *)getBasePtr(dx, dy); - tColorRGBA c00, c01, c10, c11, cswap; - c00 = *sp; - sp += 1; - c01 = *sp; - sp += (this->pitch / 4); - c11 = *sp; - sp -= 1; - c10 = *sp; - if (flipx) { - cswap = c00; c00=c01; c01=cswap; - cswap = c10; c10=c11; c11=cswap; + if (filteringMode == FILTER_BILINEAR) { + if ((dx > -1) && (dy > -1) && (dx < sw) && (dy < sh)) { + const tColorRGBA *sp = (const tColorRGBA *)getBasePtr(dx, dy); + tColorRGBA c00, c01, c10, c11, cswap; + c00 = *sp; + sp += 1; + c01 = *sp; + sp += (this->pitch / 4); + c11 = *sp; + sp -= 1; + c10 = *sp; + if (flipx) { + cswap = c00; c00=c01; c01=cswap; + cswap = c10; c10=c11; c11=cswap; + } + if (flipy) { + cswap = c00; c00=c10; c10=cswap; + cswap = c01; c01=c11; c11=cswap; + } + /* + * Interpolate colors + */ + int ex = (sdx & 0xffff); + int ey = (sdy & 0xffff); + int t1, t2; + t1 = ((((c01.r - c00.r) * ex) >> 16) + c00.r) & 0xff; + t2 = ((((c11.r - c10.r) * ex) >> 16) + c10.r) & 0xff; + pc->r = (((t2 - t1) * ey) >> 16) + t1; + t1 = ((((c01.g - c00.g) * ex) >> 16) + c00.g) & 0xff; + t2 = ((((c11.g - c10.g) * ex) >> 16) + c10.g) & 0xff; + pc->g = (((t2 - t1) * ey) >> 16) + t1; + t1 = ((((c01.b - c00.b) * ex) >> 16) + c00.b) & 0xff; + t2 = ((((c11.b - c10.b) * ex) >> 16) + c10.b) & 0xff; + pc->b = (((t2 - t1) * ey) >> 16) + t1; + t1 = ((((c01.a - c00.a) * ex) >> 16) + c00.a) & 0xff; + t2 = ((((c11.a - c10.a) * ex) >> 16) + c10.a) & 0xff; + pc->a = (((t2 - t1) * ey) >> 16) + t1; } - if (flipy) { - cswap = c00; c00=c10; c10=cswap; - cswap = c01; c01=c11; c11=cswap; + } else { + if ((dx >= 0) && (dy >= 0) && (dx < srcW) && (dy < srcH)) { + const tColorRGBA *sp = (const tColorRGBA *)getBasePtr(dx, dy); + *pc = *sp; } - /* - * Interpolate colors - */ - int ex = (sdx & 0xffff); - int ey = (sdy & 0xffff); - int t1, t2; - t1 = ((((c01.r - c00.r) * ex) >> 16) + c00.r) & 0xff; - t2 = ((((c11.r - c10.r) * ex) >> 16) + c10.r) & 0xff; - pc->r = (((t2 - t1) * ey) >> 16) + t1; - t1 = ((((c01.g - c00.g) * ex) >> 16) + c00.g) & 0xff; - t2 = ((((c11.g - c10.g) * ex) >> 16) + c10.g) & 0xff; - pc->g = (((t2 - t1) * ey) >> 16) + t1; - t1 = ((((c01.b - c00.b) * ex) >> 16) + c00.b) & 0xff; - t2 = ((((c11.b - c10.b) * ex) >> 16) + c10.b) & 0xff; - pc->b = (((t2 - t1) * ey) >> 16) + t1; - t1 = ((((c01.a - c00.a) * ex) >> 16) + c00.a) & 0xff; - t2 = ((((c11.a - c10.a) * ex) >> 16) + c10.a) & 0xff; - pc->a = (((t2 - t1) * ey) >> 16) + t1; - } -#else - if ((dx >= 0) && (dy >= 0) && (dx < srcW) && (dy < srcH)) { - const tColorRGBA *sp = (const tColorRGBA *)getBasePtr(dx, dy); - *pc = *sp; } -#endif sdx += icosx; sdy += isiny; pc++; @@ -658,6 +790,7 @@ TransparentSurface *TransparentSurface::rotoscale(const TransformStruct &transfo return target; } +template <TFilteringMode filteringMode> TransparentSurface *TransparentSurface::scale(uint16 newWidth, uint16 newHeight) const { Common::Rect srcRect(0, 0, (int16)w, (int16)h); @@ -674,176 +807,268 @@ TransparentSurface *TransparentSurface::scale(uint16 newWidth, uint16 newHeight) target->create((uint16)dstW, (uint16)dstH, this->format); -#ifdef ENABLE_BILINEAR + if (filteringMode == FILTER_BILINEAR) { - // NB: The actual order of these bytes may not be correct, but - // since all values are treated equal, that does not matter. - struct tColorRGBA { byte r; byte g; byte b; byte a; }; + // NB: The actual order of these bytes may not be correct, but + // since all values are treated equal, that does not matter. + struct tColorRGBA { byte r; byte g; byte b; byte a; }; - bool flipx = false, flipy = false; // TODO: See mirroring comment in RenderTicket ctor + bool flipx = false, flipy = false; // TODO: See mirroring comment in RenderTicket ctor - int *sax = new int[dstW + 1]; - int *say = new int[dstH + 1]; - assert(sax && say); - - /* - * Precalculate row increments - */ - int spixelw = (srcW - 1); - int spixelh = (srcH - 1); - int sx = (int) (65536.0f * (float) spixelw / (float) (dstW - 1)); - int sy = (int) (65536.0f * (float) spixelh / (float) (dstH - 1)); - - /* Maximum scaled source size */ - int ssx = (srcW << 16) - 1; - int ssy = (srcH << 16) - 1; - - /* Precalculate horizontal row increments */ - int csx = 0; - int *csax = sax; - for (int x = 0; x <= dstW; x++) { - *csax = csx; - csax++; - csx += sx; - - /* Guard from overflows */ - if (csx > ssx) { - csx = ssx; - } - } + int *sax = new int[dstW + 1]; + int *say = new int[dstH + 1]; + assert(sax && say); - /* Precalculate vertical row increments */ - int csy = 0; - int *csay = say; - for (int y = 0; y <= dstH; y++) { - *csay = csy; - csay++; - csy += sy; + /* + * Precalculate row increments + */ + int spixelw = (srcW - 1); + int spixelh = (srcH - 1); + int sx = (int) (65536.0f * (float) spixelw / (float) (dstW - 1)); + int sy = (int) (65536.0f * (float) spixelh / (float) (dstH - 1)); + + /* Maximum scaled source size */ + int ssx = (srcW << 16) - 1; + int ssy = (srcH << 16) - 1; + + /* Precalculate horizontal row increments */ + int csx = 0; + int *csax = sax; + for (int x = 0; x <= dstW; x++) { + *csax = csx; + csax++; + csx += sx; - /* Guard from overflows */ - if (csy > ssy) { - csy = ssy; + /* Guard from overflows */ + if (csx > ssx) { + csx = ssx; + } } - } - const tColorRGBA *sp = (const tColorRGBA *) getBasePtr(0, 0); - tColorRGBA *dp = (tColorRGBA *) target->getBasePtr(0, 0); - int spixelgap = srcW; + /* Precalculate vertical row increments */ + int csy = 0; + int *csay = say; + for (int y = 0; y <= dstH; y++) { + *csay = csy; + csay++; + csy += sy; + + /* Guard from overflows */ + if (csy > ssy) { + csy = ssy; + } + } - if (flipx) { - sp += spixelw; - } - if (flipy) { - sp += spixelgap * spixelh; - } + const tColorRGBA *sp = (const tColorRGBA *) getBasePtr(0, 0); + tColorRGBA *dp = (tColorRGBA *) target->getBasePtr(0, 0); + int spixelgap = srcW; - csay = say; - for (int y = 0; y < dstH; y++) { - const tColorRGBA *csp = sp; - csax = sax; - for (int x = 0; x < dstW; x++) { - /* - * Setup color source pointers - */ - int ex = (*csax & 0xffff); - int ey = (*csay & 0xffff); - int cx = (*csax >> 16); - int cy = (*csay >> 16); - - const tColorRGBA *c00, *c01, *c10, *c11; - c00 = sp; - c01 = sp; - c10 = sp; - if (cy < spixelh) { - if (flipy) { - c10 -= spixelgap; - } else { - c10 += spixelgap; + if (flipx) { + sp += spixelw; + } + if (flipy) { + sp += spixelgap * spixelh; + } + + csay = say; + for (int y = 0; y < dstH; y++) { + const tColorRGBA *csp = sp; + csax = sax; + for (int x = 0; x < dstW; x++) { + /* + * Setup color source pointers + */ + int ex = (*csax & 0xffff); + int ey = (*csay & 0xffff); + int cx = (*csax >> 16); + int cy = (*csay >> 16); + + const tColorRGBA *c00, *c01, *c10, *c11; + c00 = sp; + c01 = sp; + c10 = sp; + if (cy < spixelh) { + if (flipy) { + c10 -= spixelgap; + } else { + c10 += spixelgap; + } } - } - c11 = c10; - if (cx < spixelw) { + c11 = c10; + if (cx < spixelw) { + if (flipx) { + c01--; + c11--; + } else { + c01++; + c11++; + } + } + + /* + * Draw and interpolate colors + */ + int t1, t2; + t1 = ((((c01->r - c00->r) * ex) >> 16) + c00->r) & 0xff; + t2 = ((((c11->r - c10->r) * ex) >> 16) + c10->r) & 0xff; + dp->r = (((t2 - t1) * ey) >> 16) + t1; + t1 = ((((c01->g - c00->g) * ex) >> 16) + c00->g) & 0xff; + t2 = ((((c11->g - c10->g) * ex) >> 16) + c10->g) & 0xff; + dp->g = (((t2 - t1) * ey) >> 16) + t1; + t1 = ((((c01->b - c00->b) * ex) >> 16) + c00->b) & 0xff; + t2 = ((((c11->b - c10->b) * ex) >> 16) + c10->b) & 0xff; + dp->b = (((t2 - t1) * ey) >> 16) + t1; + t1 = ((((c01->a - c00->a) * ex) >> 16) + c00->a) & 0xff; + t2 = ((((c11->a - c10->a) * ex) >> 16) + c10->a) & 0xff; + dp->a = (((t2 - t1) * ey) >> 16) + t1; + + /* + * Advance source pointer x + */ + int *salastx = csax; + csax++; + int sstepx = (*csax >> 16) - (*salastx >> 16); if (flipx) { - c01--; - c11--; + sp -= sstepx; } else { - c01++; - c11++; + sp += sstepx; } - } - - /* - * Draw and interpolate colors - */ - int t1, t2; - t1 = ((((c01->r - c00->r) * ex) >> 16) + c00->r) & 0xff; - t2 = ((((c11->r - c10->r) * ex) >> 16) + c10->r) & 0xff; - dp->r = (((t2 - t1) * ey) >> 16) + t1; - t1 = ((((c01->g - c00->g) * ex) >> 16) + c00->g) & 0xff; - t2 = ((((c11->g - c10->g) * ex) >> 16) + c10->g) & 0xff; - dp->g = (((t2 - t1) * ey) >> 16) + t1; - t1 = ((((c01->b - c00->b) * ex) >> 16) + c00->b) & 0xff; - t2 = ((((c11->b - c10->b) * ex) >> 16) + c10->b) & 0xff; - dp->b = (((t2 - t1) * ey) >> 16) + t1; - t1 = ((((c01->a - c00->a) * ex) >> 16) + c00->a) & 0xff; - t2 = ((((c11->a - c10->a) * ex) >> 16) + c10->a) & 0xff; - dp->a = (((t2 - t1) * ey) >> 16) + t1; + /* + * Advance destination pointer x + */ + dp++; + } /* - * Advance source pointer x + * Advance source pointer y */ - int *salastx = csax; - csax++; - int sstepx = (*csax >> 16) - (*salastx >> 16); - if (flipx) { - sp -= sstepx; + int *salasty = csay; + csay++; + int sstepy = (*csay >> 16) - (*salasty >> 16); + sstepy *= spixelgap; + if (flipy) { + sp = csp - sstepy; } else { - sp += sstepx; + sp = csp + sstepy; } + } - /* - * Advance destination pointer x - */ - dp++; + delete[] sax; + delete[] say; + + } else { + + int *scaleCacheX = new int[dstW]; + for (int x = 0; x < dstW; x++) { + scaleCacheX[x] = (x * srcW) / dstW; } - /* - * Advance source pointer y - */ - int *salasty = csay; - csay++; - int sstepy = (*csay >> 16) - (*salasty >> 16); - sstepy *= spixelgap; - if (flipy) { - sp = csp - sstepy; - } else { - sp = csp + sstepy; + + for (int y = 0; y < dstH; y++) { + uint32 *destP = (uint32 *)target->getBasePtr(0, y); + const uint32 *srcP = (const uint32 *)getBasePtr(0, (y * srcH) / dstH); + for (int x = 0; x < dstW; x++) { + *destP++ = srcP[scaleCacheX[x]]; + } } + delete[] scaleCacheX; + } - delete[] sax; - delete[] say; + return target; -#else +} + +TransparentSurface *TransparentSurface::convertTo(const PixelFormat &dstFormat, const byte *palette) const { + assert(pixels); + + TransparentSurface *surface = new TransparentSurface(); - int *scaleCacheX = new int[dstW]; - for (int x = 0; x < dstW; x++) { - scaleCacheX[x] = (x * srcW) / dstW; + // If the target format is the same, just copy + if (format == dstFormat) { + surface->copyFrom(*this); + return surface; } - for (int y = 0; y < dstH; y++) { - uint32 *destP = (uint32 *)target->getBasePtr(0, y); - const uint32 *srcP = (const uint32 *)getBasePtr(0, (y * srcH) / dstH); - for (int x = 0; x < dstW; x++) { - *destP++ = srcP[scaleCacheX[x]]; + 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; + } } } - delete[] scaleCacheX; -#endif + return surface; +} - return target; +template TransparentSurface *TransparentSurface::rotoscale<FILTER_NEAREST>(const TransformStruct &transform) const; +template TransparentSurface *TransparentSurface::rotoscale<FILTER_BILINEAR>(const TransformStruct &transform) const; +template TransparentSurface *TransparentSurface::scale<FILTER_NEAREST>(uint16 newWidth, uint16 newHeight) const; +template TransparentSurface *TransparentSurface::scale<FILTER_BILINEAR>(uint16 newWidth, uint16 newHeight) const; + +TransparentSurface *TransparentSurface::rotoscale(const TransformStruct &transform) const { + return rotoscale<FILTER_BILINEAR>(transform); +} + +TransparentSurface *TransparentSurface::scale(uint16 newWidth, uint16 newHeight) const { + return scale<FILTER_NEAREST>(newWidth, newHeight); } } // End of namespace Graphics diff --git a/graphics/transparent_surface.h b/graphics/transparent_surface.h index 0cd7d5b2e9..83c65766fb 100644 --- a/graphics/transparent_surface.h +++ b/graphics/transparent_surface.h @@ -68,6 +68,11 @@ enum AlphaType { ALPHA_FULL = 2 }; +enum TFilteringMode { + FILTER_NEAREST = 0, + FILTER_BILINEAR = 1 +}; + /** * A transparent graphics surface, which implements alpha blitting. */ @@ -123,6 +128,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); /** @@ -133,8 +146,10 @@ struct TransparentSurface : public Graphics::Surface { * @param newHeight the resulting height. * @see TransformStruct */ + template <TFilteringMode filteringMode> TransparentSurface *scale(uint16 newWidth, uint16 newHeight) const; + TransparentSurface *scale(uint16 newWidth, uint16 newHeight) const; /** * @brief Rotoscale function; this returns a transformed version of this surface after rotation and * scaling. Please do not use this if angle == 0, use plain old scaling function. @@ -142,7 +157,20 @@ struct TransparentSurface : public Graphics::Surface { * @param transform a TransformStruct wrapping the required info. @see TransformStruct * */ + template <TFilteringMode filteringMode> + TransparentSurface *rotoscale(const TransformStruct &transform) const; + 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: |
