/*
 * osd.c: Abstract On Screen Display layer
 *
 * See the main source file 'vdr.c' for copyright information and
 * how to reach the author.
 *
 * $Id: osd.c 2.38 2013/02/14 15:50:19 kls Exp $
 */

#include "osd.h"
#include <math.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/unistd.h>
#include "device.h"
#include "tools.h"

tColor HsvToColor(double H, double S, double V)
{
  if (S > 0) {
     H /= 60;
     int i = floor(H);
     double f = H - i;
     double p = V * (1 - S);
     double q = V * (1 - S * f);
     double t = V * (1 - S * (1 - f));
     switch (i) {
       case 0:  return RgbToColor(V, t, p);
       case 1:  return RgbToColor(q, V, p);
       case 2:  return RgbToColor(p, V, t);
       case 3:  return RgbToColor(p, q, V);
       case 4:  return RgbToColor(t, p, V);
       default: return RgbToColor(V, p, q);
       }
     }
  else { // greyscale
     uint8_t n = V * 0xFF;
     return RgbToColor(n, n, n);
     }
}

tColor RgbShade(tColor Color, double Factor)
{
  double f = fabs(constrain(Factor, -1.0, 1.0));
  double w = Factor > 0 ? f * 0xFF : 0;
  return (Color & 0xFF000000) |
         (min(0xFF, int((1 - f) * ((Color >> 16) & 0xFF) + w + 0.5)) << 16) |
         (min(0xFF, int((1 - f) * ((Color >>  8) & 0xFF) + w + 0.5)) <<  8) |
         (min(0xFF, int((1 - f) * ( Color        & 0xFF) + w + 0.5))      );
}

#define USE_ALPHA_LUT
#ifdef USE_ALPHA_LUT
// Alpha blending with lookup table (by Reinhard Nissl <rnissl@gmx.de>)
// A little slower (138 %) on fast machines than the implementation below and faster
// on slow machines (79 %), but requires some 318KB of RAM for the lookup table.
static uint16_t AlphaLutFactors[255][256][2];
static uint8_t AlphaLutAlpha[255][256];

class cInitAlphaLut {
public:
  cInitAlphaLut(void)
  {
    for (int alphaA = 0; alphaA < 255; alphaA++) {
        int range = (alphaA == 255 ? 255 : 254);
        for (int alphaB = 0; alphaB < 256; alphaB++) {
            int alphaO_x_range = 255 * alphaA + alphaB * (range - alphaA);
            if (!alphaO_x_range)
               alphaO_x_range++;
            int factorA = (256 * 255 * alphaA + alphaO_x_range / 2) / alphaO_x_range;
            int factorB = (256 * alphaB * (range - alphaA) + alphaO_x_range / 2) / alphaO_x_range;
            AlphaLutFactors[alphaA][alphaB][0] = factorA;
            AlphaLutFactors[alphaA][alphaB][1] = factorB;
            AlphaLutAlpha[alphaA][alphaB] = alphaO_x_range / range;
            }
        }
  }
  } InitAlphaLut;

tColor AlphaBlend(tColor ColorFg, tColor ColorBg, uint8_t AlphaLayer)
{
  tColor Alpha = (ColorFg & 0xFF000000) >> 24;
  Alpha *= AlphaLayer;
  Alpha >>= 8;
  uint16_t *lut = &AlphaLutFactors[Alpha][(ColorBg & 0xFF000000) >> 24][0];
  return (tColor)((AlphaLutAlpha[Alpha][(ColorBg & 0xFF000000) >> 24] << 24)
    | (((((ColorFg & 0x00FF00FF) * lut[0] + (ColorBg & 0x00FF00FF) * lut[1])) & 0xFF00FF00)
    |  ((((ColorFg & 0x0000FF00) * lut[0] + (ColorBg & 0x0000FF00) * lut[1])) & 0x00FF0000)) >> 8);
}
#else
// Alpha blending without lookup table.
// Also works fast, but doesn't return the theoretically correct result.
// It's "good enough", though.
static tColor Multiply(tColor Color, uint8_t Alpha)
{
  tColor RB = (Color & 0x00FF00FF) * Alpha;
  RB = ((RB + ((RB >> 8) & 0x00FF00FF) + 0x00800080) >> 8) & 0x00FF00FF;
  tColor AG = ((Color >> 8) & 0x00FF00FF) * Alpha;
  AG = ((AG + ((AG >> 8) & 0x00FF00FF) + 0x00800080)) & 0xFF00FF00;
  return AG | RB;
}

tColor AlphaBlend(tColor ColorFg, tColor ColorBg, uint8_t AlphaLayer)
{
  tColor Alpha = (ColorFg & 0xFF000000) >> 24;
  if (AlphaLayer < ALPHA_OPAQUE) {
     Alpha *= AlphaLayer;
     Alpha = ((Alpha + ((Alpha >> 8) & 0x000000FF) + 0x00000080) >> 8) & 0x000000FF;
     }
  return Multiply(ColorFg, Alpha) + Multiply(ColorBg, 255 - Alpha);
}
#endif

// --- cPalette --------------------------------------------------------------

cPalette::cPalette(int Bpp)
{
  SetBpp(Bpp);
  SetAntiAliasGranularity(10, 10);
}

cPalette::~cPalette()
{
}

void cPalette::SetAntiAliasGranularity(uint FixedColors, uint BlendColors)
{
  if (FixedColors >= MAXNUMCOLORS || BlendColors == 0)
     antiAliasGranularity = MAXNUMCOLORS - 1;
  else {
     int ColorsForBlending = MAXNUMCOLORS - FixedColors;
     int ColorsPerBlend = ColorsForBlending / BlendColors + 2; // +2 = the full foreground and background colors, which are among the fixed colors
     antiAliasGranularity = double(MAXNUMCOLORS - 1) / (ColorsPerBlend - 1);
     }
}

void cPalette::Reset(void)
{
  numColors = 0;
  modified = false;
}

int cPalette::Index(tColor Color)
{
  // Check if color is already defined:
  for (int i = 0; i < numColors; i++) {
      if (color[i] == Color)
         return i;
      }
  // No exact color, try a close one:
  int i = ClosestColor(Color, 4);
  if (i >= 0)
     return i;
  // No close one, try to define a new one:
  if (numColors < maxColors) {
     color[numColors++] = Color;
     modified = true;
     return numColors - 1;
     }
  // Out of colors, so any close color must do:
  return ClosestColor(Color);
}

void cPalette::SetBpp(int Bpp)
{
  bpp = Bpp;
  maxColors = 1 << bpp;
  Reset();
}

void cPalette::SetColor(int Index, tColor Color)
{
  if (Index < maxColors) {
     if (numColors <= Index) {
        numColors = Index + 1;
        modified = true;
        }
     else
        modified |= color[Index] != Color;
     color[Index] = Color;
     }
}

const tColor *cPalette::Colors(int &NumColors) const
{
  NumColors = numColors;
  return numColors ? color : NULL;
}

void cPalette::Take(const cPalette &Palette, tIndexes *Indexes, tColor ColorFg, tColor ColorBg)
{
  for (int i = 0; i < Palette.numColors; i++) {
      tColor Color = Palette.color[i];
      if (ColorFg || ColorBg) {
         switch (i) {
           case 0: Color = ColorBg; break;
           case 1: Color = ColorFg; break;
           default: ;
           }
         }
      int n = Index(Color);
      if (Indexes)
         (*Indexes)[i] = n;
      }
}

void cPalette::Replace(const cPalette &Palette)
{
  for (int i = 0; i < Palette.numColors; i++)
      SetColor(i, Palette.color[i]);
  numColors = Palette.numColors;
  antiAliasGranularity = Palette.antiAliasGranularity;
}

tColor cPalette::Blend(tColor ColorFg, tColor ColorBg, uint8_t Level) const
{
  if (antiAliasGranularity > 0)
     Level = uint8_t(int(Level / antiAliasGranularity + 0.5) * antiAliasGranularity);
  int Af = (ColorFg & 0xFF000000) >> 24;
  int Rf = (ColorFg & 0x00FF0000) >> 16;
  int Gf = (ColorFg & 0x0000FF00) >>  8;
  int Bf = (ColorFg & 0x000000FF);
  int Ab = (ColorBg & 0xFF000000) >> 24;
  int Rb = (ColorBg & 0x00FF0000) >> 16;
  int Gb = (ColorBg & 0x0000FF00) >>  8;
  int Bb = (ColorBg & 0x000000FF);
  int A = (Ab + (Af - Ab) * Level / 0xFF) & 0xFF;
  int R = (Rb + (Rf - Rb) * Level / 0xFF) & 0xFF;
  int G = (Gb + (Gf - Gb) * Level / 0xFF) & 0xFF;
  int B = (Bb + (Bf - Bb) * Level / 0xFF) & 0xFF;
  return (A << 24) | (R << 16) | (G << 8) | B;
}

int cPalette::ClosestColor(tColor Color, int MaxDiff) const
{
  int n = 0;
  int d = INT_MAX;
  int A1 = (Color & 0xFF000000) >> 24;
  int R1 = (Color & 0x00FF0000) >> 16;
  int G1 = (Color & 0x0000FF00) >>  8;
  int B1 = (Color & 0x000000FF);
  for (int i = 0; i < numColors && d > 0; i++) {
      int A2 = (color[i] & 0xFF000000) >> 24;
      int R2 = (color[i] & 0x00FF0000) >> 16;
      int G2 = (color[i] & 0x0000FF00) >>  8;
      int B2 = (color[i] & 0x000000FF);
      int diff = 0;
      if (A1 || A2) // fully transparent colors are considered equal
         diff = (abs(A1 - A2) << 1) + (abs(R1 - R2) << 1) + (abs(G1 - G2) << 1) + (abs(B1 - B2) << 1);
      if (diff < d) {
         d = diff;
         n = i;
         }
      }
  return d <= MaxDiff ? n : -1;
}

// --- cBitmap ---------------------------------------------------------------

cBitmap::cBitmap(int Width, int Height, int Bpp, int X0, int Y0)
:cPalette(Bpp)
{
  bitmap = NULL;
  x0 = X0;
  y0 = Y0;
  width = height = 0;
  SetSize(Width, Height);
}

cBitmap::cBitmap(const char *FileName)
{
  bitmap = NULL;
  x0 = 0;
  y0 = 0;
  width = height = 0;
  LoadXpm(FileName);
}

cBitmap::cBitmap(const char *const Xpm[])
{
  bitmap = NULL;
  x0 = 0;
  y0 = 0;
  width = height = 0;
  SetXpm(Xpm);
}

cBitmap::~cBitmap()
{
  free(bitmap);
}

void cBitmap::SetSize(int Width, int Height)
{
  if (bitmap && Width == width && Height == height)
     return;
  width = Width;
  height = Height;
  free(bitmap);
  bitmap = NULL;
  dirtyX1 = 0;
  dirtyY1 = 0;
  dirtyX2 = width - 1;
  dirtyY2 = height - 1;
  if (width > 0 && height > 0) {
     bitmap = MALLOC(tIndex, width * height);
     if (bitmap)
        memset(bitmap, 0x00, width * height);
     else
        esyslog("ERROR: can't allocate bitmap!");
     }
  else
     esyslog("ERROR: invalid bitmap parameters (%d, %d)!", width, height);
}

bool cBitmap::Contains(int x, int y) const
{
  x -= x0;
  y -= y0;
  return 0 <= x && x < width && 0 <= y && y < height;
}

bool cBitmap::Covers(int x1, int y1, int x2, int y2) const
{
  x1 -= x0;
  y1 -= y0;
  x2 -= x0;
  y2 -= y0;
  return x1 <= 0 && y1 <= 0 && x2 >= width - 1 && y2 >= height - 1;
}

bool cBitmap::Intersects(int x1, int y1, int x2, int y2) const
{
  x1 -= x0;
  y1 -= y0;
  x2 -= x0;
  y2 -= y0;
  return !(x2 < 0 || x1 >= width || y2 < 0 || y1 >= height);
}

bool cBitmap::Dirty(int &x1, int &y1, int &x2, int &y2)
{
  if (dirtyX2 >= 0) {
     x1 = dirtyX1;
     y1 = dirtyY1;
     x2 = dirtyX2;
     y2 = dirtyY2;
     return true;
     }
  return false;
}

void cBitmap::Clean(void)
{
  dirtyX1 = width;
  dirtyY1 = height;
  dirtyX2 = -1;
  dirtyY2 = -1;
}

bool cBitmap::LoadXpm(const char *FileName)
{
  bool Result = false;
  FILE *f = fopen(FileName, "r");
  if (f) {
     char **Xpm = NULL;
     bool isXpm = false;
     int lines = 0;
     int index = 0;
     char *s;
     cReadLine ReadLine;
     while ((s = ReadLine.Read(f)) != NULL) {
           s = skipspace(s);
           if (!isXpm) {
              if (strcmp(s, "/* XPM */") != 0) {
                 esyslog("ERROR: invalid header in XPM file '%s'", FileName);
                 break;
                 }
              isXpm = true;
              }
           else if (*s++ == '"') {
              if (!lines) {
                 int w, h, n, c;
                 if (4 != sscanf(s, "%d %d %d %d", &w, &h, &n, &c)) {
                    esyslog("ERROR: faulty 'values' line in XPM file '%s'", FileName);
                    isXpm = false;
                    break;
                    }
                 lines = h + n + 1;
                 Xpm = MALLOC(char *, lines);
                 memset(Xpm, 0, lines * sizeof(char*));
                 }
              char *q = strchr(s, '"');
              if (!q) {
                 esyslog("ERROR: missing quotes in XPM file '%s'", FileName);
                 isXpm = false;
                 break;
                 }
              *q = 0;
              if (index < lines)
                 Xpm[index++] = strdup(s);
              else {
                 esyslog("ERROR: too many lines in XPM file '%s'", FileName);
                 isXpm = false;
                 break;
                 }
              }
           }
     if (isXpm) {
        if (index == lines)
           Result = SetXpm(Xpm);
        else
           esyslog("ERROR: too few lines in XPM file '%s'", FileName);
        }
     if (Xpm) {
        for (int i = 0; i < index; i++)
            free(Xpm[i]);
        }
     free(Xpm);
     fclose(f);
     }
  else
     esyslog("ERROR: can't open XPM file '%s'", FileName);
  return Result;
}

bool cBitmap::SetXpm(const char *const Xpm[], bool IgnoreNone)
{
  if (!Xpm)
     return false;
  const char *const *p = Xpm;
  int w, h, n, c;
  if (4 != sscanf(*p, "%d %d %d %d", &w, &h, &n, &c)) {
     esyslog("ERROR: faulty 'values' line in XPM: '%s'", *p);
     return false;
     }
  if (n > MAXNUMCOLORS) {
     esyslog("ERROR: too many colors in XPM: %d", n);
     return false;
     }
  int b = 0;
  while (1 << (1 << b) < (IgnoreNone ? n - 1 : n))
        b++;
  SetBpp(1 << b);
  SetSize(w, h);
  int NoneColorIndex = MAXNUMCOLORS;
  for (int i = 0; i < n; i++) {
      const char *s = *++p;
      if (int(strlen(s)) < c) {
         esyslog("ERROR: faulty 'colors' line in XPM: '%s'", s);
         return false;
         }
      s = skipspace(s + c);
      if (*s != 'c') {
         esyslog("ERROR: unknown color key in XPM: '%c'", *s);
         return false;
         }
      s = skipspace(s + 1);
      if (strcasecmp(s, "none") == 0) {
         NoneColorIndex = i;
         if (!IgnoreNone)
            SetColor(i, clrTransparent);
         continue;
         }
      if (*s != '#') {
         esyslog("ERROR: unknown color code in XPM: '%c'", *s);
         return false;
         }
      tColor color = strtoul(++s, NULL, 16) | 0xFF000000;
      SetColor((IgnoreNone && i > NoneColorIndex) ? i - 1 : i, color);
      }
  for (int y = 0; y < h; y++) {
      const char *s = *++p;
      if (int(strlen(s)) != w * c) {
         esyslog("ERROR: faulty pixel line in XPM: %d '%s'", y, s);
         return false;
         }
      for (int x = 0; x < w; x++) {
          for (int i = 0; i <= n; i++) {
              if (i == n) {
                 esyslog("ERROR: undefined pixel color in XPM: %d %d '%s'", x, y, s);
                 return false;
                 }
              if (strncmp(Xpm[i + 1], s, c) == 0) {
                 if (i == NoneColorIndex)
                    NoneColorIndex = MAXNUMCOLORS;
                 SetIndex(x, y, (IgnoreNone && i > NoneColorIndex) ? i - 1 : i);
                 break;
                 }
              }
          s += c;
          }
      }
  if (NoneColorIndex < MAXNUMCOLORS && !IgnoreNone)
     return SetXpm(Xpm, true);
  return true;
}

void cBitmap::SetIndex(int x, int y, tIndex Index)
{
  if (bitmap) {
     if (0 <= x && x < width && 0 <= y && y < height) {
        if (bitmap[width * y + x] != Index) {
           bitmap[width * y + x] = Index;
           if (dirtyX1 > x)  dirtyX1 = x;
           if (dirtyY1 > y)  dirtyY1 = y;
           if (dirtyX2 < x)  dirtyX2 = x;
           if (dirtyY2 < y)  dirtyY2 = y;
           }
        }
     }
}

void cBitmap::DrawPixel(int x, int y, tColor Color)
{
  x -= x0;
  y -= y0;
  SetIndex(x, y, Index(Color));
}

void cBitmap::DrawBitmap(int x, int y, const cBitmap &Bitmap, tColor ColorFg, tColor ColorBg, bool ReplacePalette, bool Overlay)
{
  if (bitmap && Bitmap.bitmap && Intersects(x, y, x + Bitmap.Width() - 1, y + Bitmap.Height() - 1)) {
     if (Covers(x, y, x + Bitmap.Width() - 1, y + Bitmap.Height() - 1))
        Reset();
     x -= x0;
     y -= y0;
     if (ReplacePalette && Covers(x + x0, y + y0, x + x0 + Bitmap.Width() - 1, y + y0 + Bitmap.Height() - 1)) {
        Replace(Bitmap);
        for (int ix = 0; ix < Bitmap.width; ix++) {
            for (int iy = 0; iy < Bitmap.height; iy++) {
                if (!Overlay || Bitmap.bitmap[Bitmap.width * iy + ix] != 0)
                   SetIndex(x + ix, y + iy, Bitmap.bitmap[Bitmap.width * iy + ix]);
                }
            }
        }
     else {
        tIndexes Indexes;
        Take(Bitmap, &Indexes, ColorFg, ColorBg);
        for (int ix = 0; ix < Bitmap.width; ix++) {
            for (int iy = 0; iy < Bitmap.height; iy++) {
                if (!Overlay || Bitmap.bitmap[Bitmap.width * iy + ix] != 0)
                   SetIndex(x + ix, y + iy, Indexes[int(Bitmap.bitmap[Bitmap.width * iy + ix])]);
                }
            }
        }
     }
}

void cBitmap::DrawText(int x, int y, const char *s, tColor ColorFg, tColor ColorBg, const cFont *Font, int Width, int Height, int Alignment)
{
  if (bitmap) {
     int w = Font->Width(s);
     int h = Font->Height();
     int limit = 0;
     int cw = Width ? Width : w;
     int ch = Height ? Height : h;
     if (!Intersects(x, y, x + cw - 1, y + ch - 1))
        return;
     if (ColorBg != clrTransparent)
        DrawRectangle(x, y, x + cw - 1, y + ch - 1, ColorBg);
     if (Width || Height) {
        limit = x + cw - x0;
        if (Width) {
           if ((Alignment & taLeft) != 0) {
              if ((Alignment & taBorder) != 0)
                 x += max(h / TEXT_ALIGN_BORDER, 1);
              }
           else if ((Alignment & taRight) != 0) {
              if (w < Width)
                 x += Width - w;
              if ((Alignment & taBorder) != 0)
                 x -= max(h / TEXT_ALIGN_BORDER, 1);
              }
           else { // taCentered
              if (w < Width)
                 x += (Width - w) / 2;
              }
           }
        if (Height) {
           if ((Alignment & taTop) != 0)
              ;
           else if ((Alignment & taBottom) != 0) {
              if (h < Height)
                 y += Height - h;
              }
           else { // taCentered
              if (h < Height)
                 y += (Height - h) / 2;
              }
           }
        }
     x -= x0;
     y -= y0;
     Font->DrawText(this, x, y, s, ColorFg, ColorBg, limit);
     }
}

void cBitmap::DrawRectangle(int x1, int y1, int x2, int y2, tColor Color)
{
  if (bitmap && Intersects(x1, y1, x2, y2)) {
     if (Covers(x1, y1, x2, y2))
        Reset();
     x1 -= x0;
     y1 -= y0;
     x2 -= x0;
     y2 -= y0;
     x1 = max(x1, 0);
     y1 = max(y1, 0);
     x2 = min(x2, width - 1);
     y2 = min(y2, height - 1);
     tIndex c = Index(Color);
     for (int y = y1; y <= y2; y++) {
         for (int x = x1; x <= x2; x++)
             SetIndex(x, y, c);
         }
     }
}

void cBitmap::DrawEllipse(int x1, int y1, int x2, int y2, tColor Color, int Quadrants)
{
  if (!Intersects(x1, y1, x2, y2))
     return;
  // Algorithm based on http://homepage.smc.edu/kennedy_john/BELIPSE.PDF
  int rx = x2 - x1;
  int ry = y2 - y1;
  int cx = (x1 + x2) / 2;
  int cy = (y1 + y2) / 2;
  switch (abs(Quadrants)) {
    case 0: rx /= 2; ry /= 2; break;
    case 1: cx = x1; cy = y2; break;
    case 2: cx = x2; cy = y2; break;
    case 3: cx = x2; cy = y1; break;
    case 4: cx = x1; cy = y1; break;
    case 5: cx = x1;          ry /= 2; break;
    case 6:          cy = y2; rx /= 2; break;
    case 7: cx = x2;          ry /= 2; break;
    case 8:          cy = y1; rx /= 2; break;
    default: ;
    }
  int TwoASquare = 2 * rx * rx;
  int TwoBSquare = 2 * ry * ry;
  int x = rx;
  int y = 0;
  int XChange = ry * ry * (1 - 2 * rx);
  int YChange = rx * rx;
  int EllipseError = 0;
  int StoppingX = TwoBSquare * rx;
  int StoppingY = 0;
  while (StoppingX >= StoppingY) {
        switch (Quadrants) {
          case  5: DrawRectangle(cx,     cy + y, cx + x, cy + y, Color); // no break
          case  1: DrawRectangle(cx,     cy - y, cx + x, cy - y, Color); break;
          case  7: DrawRectangle(cx - x, cy + y, cx,     cy + y, Color); // no break
          case  2: DrawRectangle(cx - x, cy - y, cx,     cy - y, Color); break;
          case  3: DrawRectangle(cx - x, cy + y, cx,     cy + y, Color); break;
          case  4: DrawRectangle(cx,     cy + y, cx + x, cy + y, Color); break;
          case  0:
          case  6: DrawRectangle(cx - x, cy - y, cx + x, cy - y, Color); if (Quadrants == 6) break;
          case  8: DrawRectangle(cx - x, cy + y, cx + x, cy + y, Color); break;
          case -1: DrawRectangle(cx + x, cy - y, x2,     cy - y, Color); break;
          case -2: DrawRectangle(x1,     cy - y, cx - x, cy - y, Color); break;
          case -3: DrawRectangle(x1,     cy + y, cx - x, cy + y, Color); break;
          case -4: DrawRectangle(cx + x, cy + y, x2,     cy + y, Color); break;
          default: ;
          }
        y++;
        StoppingY += TwoASquare;
        EllipseError += YChange;
        YChange += TwoASquare;
        if (2 * EllipseError + XChange > 0) {
           x--;
           StoppingX -= TwoBSquare;
           EllipseError += XChange;
           XChange += TwoBSquare;
           }
        }
  x = 0;
  y = ry;
  XChange = ry * ry;
  YChange = rx * rx * (1 - 2 * ry);
  EllipseError = 0;
  StoppingX = 0;
  StoppingY = TwoASquare * ry;
  while (StoppingX <= StoppingY) {
        switch (Quadrants) {
          case  5: DrawRectangle(cx,     cy + y, cx + x, cy + y, Color); // no break
          case  1: DrawRectangle(cx,     cy - y, cx + x, cy - y, Color); break;
          case  7: DrawRectangle(cx - x, cy + y, cx,     cy + y, Color); // no break
          case  2: DrawRectangle(cx - x, cy - y, cx,     cy - y, Color); break;
          case  3: DrawRectangle(cx - x, cy + y, cx,     cy + y, Color); break;
          case  4: DrawRectangle(cx,     cy + y, cx + x, cy + y, Color); break;
          case  0:
          case  6: DrawRectangle(cx - x, cy - y, cx + x, cy - y, Color); if (Quadrants == 6) break;
          case  8: DrawRectangle(cx - x, cy + y, cx + x, cy + y, Color); break;
          case -1: DrawRectangle(cx + x, cy - y, x2,     cy - y, Color); break;
          case -2: DrawRectangle(x1,     cy - y, cx - x, cy - y, Color); break;
          case -3: DrawRectangle(x1,     cy + y, cx - x, cy + y, Color); break;
          case -4: DrawRectangle(cx + x, cy + y, x2,     cy + y, Color); break;
          default: ;
          }
        x++;
        StoppingX += TwoBSquare;
        EllipseError += XChange;
        XChange += TwoBSquare;
        if (2 * EllipseError + YChange > 0) {
           y--;
           StoppingY -= TwoASquare;
           EllipseError += YChange;
           YChange += TwoASquare;
           }
        }
}

void cBitmap::DrawSlope(int x1, int y1, int x2, int y2, tColor Color, int Type)
{
  if (!Intersects(x1, y1, x2, y2))
     return;
  bool upper    = Type & 0x01;
  bool falling  = Type & 0x02;
  bool vertical = Type & 0x04;
  if (vertical) {
     for (int y = y1; y <= y2; y++) {
         double c = cos((y - y1) * M_PI / (y2 - y1 + 1));
         if (falling)
            c = -c;
         int x = int((x2 - x1 + 1) * c / 2);
         if (upper && !falling || !upper && falling)
            DrawRectangle(x1, y, (x1 + x2) / 2 + x, y, Color);
         else
            DrawRectangle((x1 + x2) / 2 + x, y, x2, y, Color);
         }
     }
  else {
     for (int x = x1; x <= x2; x++) {
         double c = cos((x - x1) * M_PI / (x2 - x1 + 1));
         if (falling)
            c = -c;
         int y = int((y2 - y1 + 1) * c / 2);
         if (upper)
            DrawRectangle(x, y1, x, (y1 + y2) / 2 + y, Color);
         else
            DrawRectangle(x, (y1 + y2) / 2 + y, x, y2, Color);
         }
     }
}

const tIndex *cBitmap::Data(int x, int y) const
{
  return &bitmap[y * width + x];
}

void cBitmap::ReduceBpp(const cPalette &Palette)
{
  int NewBpp = Palette.Bpp();
  if (Bpp() == 4 && NewBpp == 2) {
     for (int i = width * height; i--; ) {
         tIndex p = bitmap[i];
         bitmap[i] = (p >> 2) | ((p & 0x03) != 0);
         }
     }
  else if (Bpp() == 8) {
     if (NewBpp == 2) {
        for (int i = width * height; i--; ) {
            tIndex p = bitmap[i];
            bitmap[i] = (p >> 6) | ((p & 0x30) != 0);
            }
        }
     else if (NewBpp == 4) {
        for (int i = width * height; i--; ) {
            tIndex p = bitmap[i];
            bitmap[i] = p >> 4;
            }
        }
     else
        return;
     }
  else
     return;
  SetBpp(NewBpp);
  Replace(Palette);
}

void cBitmap::ShrinkBpp(int NewBpp)
{
  int NumOldColors;
  const tColor *Colors = this->Colors(NumOldColors);
  if (Colors) {
     // Find the most frequently used colors and create a map table:
     int Used[MAXNUMCOLORS] = { 0 };
     int Map[MAXNUMCOLORS] = { 0 };
     for (int i = width * height; i--; )
         Used[bitmap[i]]++;
     int MaxNewColors = (NewBpp == 4) ? 16 : 4;
     cPalette NewPalette(NewBpp);
     for (int i = 0; i < MaxNewColors; i++) {
         int Max = 0;
         int Index = -1;
         for (int n = 0; n < NumOldColors; n++) {
             if (Used[n] > Max) {
                Max = Used[n];
                Index = n;
                }
             }
         if (Index >= 0) {
            Used[Index] = 0;
            Map[Index] = i;
            NewPalette.SetColor(i, Colors[Index]);
            }
         else
            break;
         }
     // Complete the map table for all other colors (will be set to closest match):
     for (int n = 0; n < NumOldColors; n++) {
         if (Used[n])
            Map[n] = NewPalette.Index(Colors[n]);
         }
     // Do the actual index mapping:
     for (int i = width * height; i--; )
         bitmap[i] = Map[bitmap[i]];
     SetBpp(NewBpp);
     Replace(NewPalette);
     }
}

cBitmap *cBitmap::Scaled(double FactorX, double FactorY, bool AntiAlias)
{
  // Fixed point scaling code based on www.inversereality.org/files/bitmapscaling.pdf
  // by deltener@mindtremors.com
  cBitmap *b = new cBitmap(int(round(Width() * FactorX)), int(round(Height() * FactorY)), Bpp(), X0(), Y0());
  int RatioX = (Width() << 16) / b->Width();
  int RatioY = (Height() << 16) / b->Height();
  if (!AntiAlias || FactorX <= 1.0 && FactorY <= 1.0) {
     // Downscaling - no anti-aliasing:
     b->Replace(*this); // copy palette
     tIndex *DestRow = b->bitmap;
     int SourceY = 0;
     for (int y = 0; y < b->Height(); y++) {
         int SourceX = 0;
         tIndex *SourceRow = bitmap + (SourceY >> 16) * Width();
         tIndex *Dest = DestRow;
         for (int x = 0; x < b->Width(); x++) {
             *Dest++ = SourceRow[SourceX >> 16];
             SourceX += RatioX;
             }
         SourceY += RatioY;
         DestRow += b->Width();
         }
     }
  else {
     // Upscaling - anti-aliasing:
     b->SetBpp(8);
     b->Replace(*this); // copy palette (must be done *after* SetBpp()!)
     int SourceY = 0;
     for (int y = 0; y < b->Height(); y++) {
         int SourceX = 0;
         int sy = min(SourceY >> 16, Height() - 2);
         uint8_t BlendY = 0xFF - ((SourceY >> 8) & 0xFF);
         for (int x = 0; x < b->Width(); x++) {
             int sx = min(SourceX >> 16, Width() - 2);
             uint8_t BlendX = 0xFF - ((SourceX >> 8) & 0xFF);
             tColor c1 = b->Blend(GetColor(sx, sy),     GetColor(sx + 1, sy),     BlendX);
             tColor c2 = b->Blend(GetColor(sx, sy + 1), GetColor(sx + 1, sy + 1), BlendX);
             tColor c3 = b->Blend(c1, c2, BlendY);
             b->DrawPixel(x + X0(), y + Y0(), c3);
             SourceX += RatioX;
             }
         SourceY += RatioY;
         }
     }
  return b;
}

// --- cRect -----------------------------------------------------------------

const cRect cRect::Null;

void cRect::Grow(int Dx, int Dy)
{
  point.Shift(-Dx, -Dy);
  size.Grow(Dx, Dy);
}

bool cRect::Contains(const cPoint &Point) const
{
  return Left() <= Point.X() &&
         Top() <= Point.Y() &&
         Right() >= Point.X() &&
         Bottom() >= Point.Y();
}

bool cRect::Contains(const cRect &Rect) const
{
  return Left() <= Rect.Left() &&
         Top() <= Rect.Top() &&
         Right() >= Rect.Right() &&
         Bottom() >= Rect.Bottom();
}

bool cRect::Intersects(const cRect &Rect) const
{
  return !(Left() > Rect.Right() ||
           Top() > Rect.Bottom() ||
           Right() < Rect.Left() ||
           Bottom() < Rect.Top());
}

cRect cRect::Intersected(const cRect &Rect) const
{
  cRect r;
  if (!IsEmpty() && !Rect.IsEmpty()) {
     r.SetLeft(max(Left(), Rect.Left()));
     r.SetTop(max(Top(), Rect.Top()));
     r.SetRight(min(Right(), Rect.Right()));
     r.SetBottom(min(Bottom(), Rect.Bottom()));
     }
  return r;
}

void cRect::Combine(const cRect &Rect)
{
  if (IsEmpty())
     *this = Rect;
  if (Rect.IsEmpty())
     return;
  // must set right/bottom *before* top/left!
  SetRight(max(Right(), Rect.Right()));
  SetBottom(max(Bottom(), Rect.Bottom()));
  SetLeft(min(Left(), Rect.Left()));
  SetTop(min(Top(), Rect.Top()));
}

void cRect::Combine(const cPoint &Point)
{
  if (IsEmpty())
     Set(Point.X(), Point.Y(), 1, 1);
  // must set right/bottom *before* top/left!
  SetRight(max(Right(), Point.X()));
  SetBottom(max(Bottom(), Point.Y()));
  SetLeft(min(Left(), Point.X()));
  SetTop(min(Top(), Point.Y()));
}

// --- cPixmap ---------------------------------------------------------------

cMutex cPixmap::mutex;

cPixmap::cPixmap(void)
{
  layer = -1;
  alpha = ALPHA_OPAQUE;
  tile = false;
}

cPixmap::cPixmap(int Layer, const cRect &ViewPort, const cRect &DrawPort)
{
  layer = Layer;
  if (layer >= MAXPIXMAPLAYERS) {
     layer = MAXPIXMAPLAYERS - 1;
     esyslog("ERROR: pixmap layer %d limited to %d", Layer, layer);
     }
  viewPort = ViewPort;
  if (!DrawPort.IsEmpty())
     drawPort = DrawPort;
  else {
     drawPort = viewPort;
     drawPort.SetPoint(0, 0);
     }
  alpha = ALPHA_OPAQUE;
  tile = false;
}

void cPixmap::MarkViewPortDirty(const cRect &Rect)
{
  dirtyViewPort.Combine(Rect.Intersected(viewPort));
}

void cPixmap::MarkViewPortDirty(const cPoint &Point)
{
  if (viewPort.Contains(Point))
     dirtyViewPort.Combine(Point);
}

void cPixmap::MarkDrawPortDirty(const cRect &Rect)
{
  dirtyDrawPort.Combine(Rect.Intersected(drawPort));
  if (tile)
     MarkViewPortDirty(viewPort);
  else
     MarkViewPortDirty(Rect.Shifted(viewPort.Point()));
}

void cPixmap::MarkDrawPortDirty(const cPoint &Point)
{
  if (drawPort.Contains(Point)) {
     dirtyDrawPort.Combine(Point);
     if (tile)
        MarkViewPortDirty(viewPort);
     else
        MarkViewPortDirty(Point.Shifted(viewPort.Point()));
     }
}

void cPixmap::SetClean(void)
{
  dirtyViewPort = dirtyDrawPort = cRect();
}

void cPixmap::SetLayer(int Layer)
{
  Lock();
  if (Layer >= MAXPIXMAPLAYERS) {
     esyslog("ERROR: pixmap layer %d limited to %d", Layer, MAXPIXMAPLAYERS - 1);
     Layer = MAXPIXMAPLAYERS - 1;
     }
  if (Layer != layer) {
     if (Layer > 0 || layer > 0)
        MarkViewPortDirty(viewPort);
     layer = Layer;
     }
  Unlock();
}

void cPixmap::SetAlpha(int Alpha)
{
  Lock();
  Alpha = constrain(Alpha, ALPHA_TRANSPARENT, ALPHA_OPAQUE);
  if (Alpha != alpha) {
     MarkViewPortDirty(viewPort);
     alpha = Alpha;
     }
  Unlock();
}

void cPixmap::SetTile(bool Tile)
{
  Lock();
  if (Tile != tile) {
     if (drawPort.Point() != cPoint(0, 0) || drawPort.Width() < viewPort.Width() || drawPort.Height() < viewPort.Height())
        MarkViewPortDirty(viewPort);
     tile = Tile;
     }
  Unlock();
}

void cPixmap::SetViewPort(const cRect &Rect)
{
  Lock();
  if (Rect != viewPort) {
     if (tile)
        MarkViewPortDirty(viewPort);
     else
        MarkViewPortDirty(drawPort.Shifted(viewPort.Point()));
     viewPort = Rect;
     if (tile)
        MarkViewPortDirty(viewPort);
     else
        MarkViewPortDirty(drawPort.Shifted(viewPort.Point()));
     }
  Unlock();
}

void cPixmap::SetDrawPortPoint(const cPoint &Point, bool Dirty)
{
  Lock();
  if (Point != drawPort.Point()) {
     if (Dirty) {
        if (tile)
           MarkViewPortDirty(viewPort);
        else
           MarkViewPortDirty(drawPort.Shifted(viewPort.Point()));
        }
     drawPort.SetPoint(Point);
     if (Dirty && !tile)
        MarkViewPortDirty(drawPort.Shifted(viewPort.Point()));
     }
  Unlock();
}

// --- cImage ----------------------------------------------------------------

cImage::cImage(void)
{
  data = NULL;
}

cImage::cImage(const cImage &Image)
{
  size = Image.Size();
  int l = size.Width() * size.Height() * sizeof(tColor);
  data = MALLOC(tColor, l);
  memcpy(data, Image.Data(), l);
}

cImage::cImage(const cSize &Size, const tColor *Data)
{
  size = Size;
  int l = size.Width() * size.Height() * sizeof(tColor);
  data = MALLOC(tColor, l);
  if (Data)
     memcpy(data, Data, l);
}

cImage::~cImage()
{
  free(data);
}

void cImage::Clear(void)
{
  memset(data, 0x00, Width() * Height() * sizeof(tColor));
}

void cImage::Fill(tColor Color)
{
  for (int i = Width() * Height() - 1; i >= 0; i--)
      data[i] = Color;
}

// --- cPixmapMemory ---------------------------------------------------------

cPixmapMemory::cPixmapMemory(void)
{
  data = NULL;
  panning = false;
}

cPixmapMemory::cPixmapMemory(int Layer, const cRect &ViewPort, const cRect &DrawPort)
:cPixmap(Layer, ViewPort, DrawPort)
{
  data = MALLOC(tColor, this->DrawPort().Width() * this->DrawPort().Height());
}

cPixmapMemory::~cPixmapMemory()
{
  free(data);
}

void cPixmapMemory::Clear(void)
{
  Lock();
  memset(data, 0x00, DrawPort().Width() * DrawPort().Height() * sizeof(tColor));
  MarkDrawPortDirty(DrawPort());
  Unlock();
}

void cPixmapMemory::Fill(tColor Color)
{
  Lock();
  for (int i = DrawPort().Width() * DrawPort().Height() - 1; i >= 0; i--)
      data[i] = Color;
  MarkDrawPortDirty(DrawPort());
  Unlock();
}

void cPixmap::DrawPixmap(const cPixmap *Pixmap, const cRect &Dirty)
{
  if (Pixmap->Tile() && (Pixmap->DrawPort().Point() != cPoint(0, 0) || Pixmap->DrawPort().Size() < Pixmap->ViewPort().Size())) {
     cPoint t0 = Pixmap->DrawPort().Point().Shifted(Pixmap->ViewPort().Point()); // the origin of the draw port in absolute OSD coordinates
     // Find the top/leftmost location where the draw port touches the view port:
     while (t0.X() > Pixmap->ViewPort().Left())
           t0.Shift(-Pixmap->DrawPort().Width(), 0);
     while (t0.Y() > Pixmap->ViewPort().Top())
           t0.Shift(0, -Pixmap->DrawPort().Height());
     cPoint t = t0;;
     while (t.Y() <= Pixmap->ViewPort().Bottom()) {
           while (t.X() <= Pixmap->ViewPort().Right()) {
                 cRect Source = Pixmap->DrawPort(); // assume the entire pixmap needs to be rendered
                 Source.Shift(Pixmap->ViewPort().Point()); // Source is now in absolute OSD coordinates
                 cPoint Delta = Source.Point() - t;
                 Source.SetPoint(t); // Source is now where the pixmap's data shall be drawn
                 Source = Source.Intersected(Pixmap->ViewPort()); // Source is now limited to the pixmap's view port
                 Source = Source.Intersected(Dirty); // Source is now limited to the actual dirty rectangle
                 if (!Source.IsEmpty()) {
                    cPoint Dest = Source.Point().Shifted(-ViewPort().Point()); // remember the destination point
                    Source.Shift(Delta); // Source is now back at the pixmap's draw port location, still in absolute OSD coordinates
                    Source.Shift(-Pixmap->ViewPort().Point()); // Source is now relative to the pixmap's view port again
                    Source.Shift(-Pixmap->DrawPort().Point()); // Source is now relative to the pixmap's data
                    if (Pixmap->Layer() == 0)
                       Copy(Pixmap, Source, Dest); // this is the "background" pixmap
                    else
                       Render(Pixmap, Source, Dest); // all others are alpha blended over the background
                    }
                 t.Shift(Pixmap->DrawPort().Width(), 0); // increase one draw port width to the right
                 }
           t.SetX(t0.X()); // go back to the leftmost position
           t.Shift(0, Pixmap->DrawPort().Height()); // increase one draw port height down
           }
     }
  else {
     cRect Source = Pixmap->DrawPort(); // assume the entire pixmap needs to be rendered
     Source.Shift(Pixmap->ViewPort().Point()); // Source is now in absolute OSD coordinates
     Source = Source.Intersected(Pixmap->ViewPort()); // Source is now limited to the pixmap's view port
     Source = Source.Intersected(Dirty); // Source is now limited to the actual dirty rectangle
     if (!Source.IsEmpty()) {
        cPoint Dest = Source.Point().Shifted(-ViewPort().Point()); // remember the destination point
        Source.Shift(-Pixmap->ViewPort().Point()); // Source is now relative to the pixmap's draw port again
        Source.Shift(-Pixmap->DrawPort().Point()); // Source is now relative to the pixmap's data
        if (Pixmap->Layer() == 0)
           Copy(Pixmap, Source, Dest); // this is the "background" pixmap
        else
           Render(Pixmap, Source, Dest); // all others are alpha blended over the background
        }
     }
}

void cPixmapMemory::DrawImage(const cPoint &Point, const cImage &Image)
{
  Lock();
  cRect r = cRect(Point, Image.Size()).Intersected(DrawPort().Size());
  if (!r.IsEmpty()) {
     int ws = Image.Size().Width();
     int wd = DrawPort().Width();
     int w = r.Width() * sizeof(tColor);
     const tColor *ps = Image.Data();
     if (Point.Y() < 0)
        ps -= Point.Y() * ws;
     if (Point.X() < 0)
        ps -= Point.X();
     tColor *pd = data + wd * r.Top() + r.Left();
     for (int y = r.Height(); y-- > 0; ) {
         memcpy(pd, ps, w);
         ps += ws;
         pd += wd;
         }
     MarkDrawPortDirty(r);
     }
  Unlock();
}

void cPixmapMemory::DrawImage(const cPoint &Point, int ImageHandle)
{
  Lock();
  if (const cImage *Image = cOsdProvider::GetImageData(ImageHandle))
     DrawImage(Point, *Image);
  Unlock();
}

void cPixmapMemory::DrawPixel(const cPoint &Point, tColor Color)
{
  Lock();
  if (DrawPort().Size().Contains(Point)) {
     int p = Point.Y() * DrawPort().Width() + Point.X();
     if (Layer() == 0 && !IS_OPAQUE(Color))
        data[p] = AlphaBlend(Color, data[p]);
     else
        data[p] = Color;
     MarkDrawPortDirty(Point);
     }
  Unlock();
}

void cPixmapMemory::DrawBitmap(const cPoint &Point, const cBitmap &Bitmap, tColor ColorFg, tColor ColorBg, bool Overlay)
{
  Lock();
  cRect r = cRect(Point, cSize(Bitmap.Width(), Bitmap.Height())).Intersected(DrawPort().Size());
  if (!r.IsEmpty()) {
     bool UseColors = ColorFg || ColorBg;
     int wd = DrawPort().Width();
     tColor *pd = data + wd * r.Top() + r.Left();
     for (int y = r.Top(); y <= r.Bottom(); y++) {
         tColor *cd = pd;
         for (int x = r.Left(); x <= r.Right(); x++) {
             tIndex Index = *Bitmap.Data(x - Point.X(), y - Point.Y());
             if (Index || !Overlay) {
                if (UseColors)
                   *cd = Index ? ColorFg : ColorBg;
                else
                   *cd = Bitmap.Color(Index);
                }
             cd++;
             }
         pd += wd;
         }
     MarkDrawPortDirty(r);
     }
  Unlock();
}

void cPixmapMemory::DrawText(const cPoint &Point, const char *s, tColor ColorFg, tColor ColorBg, const cFont *Font, int Width, int Height, int Alignment)
{
  Lock();
  int x = Point.X();
  int y = Point.Y();
  int w = Font->Width(s);
  int h = Font->Height();
  int limit = 0;
  int cw = Width ? Width : w;
  int ch = Height ? Height : h;
  cRect r(x, y, cw, ch);
  if (ColorBg != clrTransparent)
     DrawRectangle(r, ColorBg);
  if (Width || Height) {
     limit = x + cw;
     if (Width) {
        if ((Alignment & taLeft) != 0) {
           if ((Alignment & taBorder) != 0)
              x += max(h / TEXT_ALIGN_BORDER, 1);
           }
        else if ((Alignment & taRight) != 0) {
           if (w < Width)
              x += Width - w;
           if ((Alignment & taBorder) != 0)
              x -= max(h / TEXT_ALIGN_BORDER, 1);
           }
        else { // taCentered
           if (w < Width)
              x += (Width - w) / 2;
           }
        }
     if (Height) {
        if ((Alignment & taTop) != 0)
           ;
        else if ((Alignment & taBottom) != 0) {
           if (h < Height)
              y += Height - h;
           }
        else { // taCentered
           if (h < Height)
              y += (Height - h) / 2;
           }
        }
     }
  Font->DrawText(this, x, y, s, ColorFg, ColorBg, limit);
  MarkDrawPortDirty(r);
  Unlock();
}

void cPixmapMemory::DrawRectangle(const cRect &Rect, tColor Color)
{
  Lock();
  cRect r = Rect.Intersected(DrawPort().Size());
  if (!r.IsEmpty()) {
     int wd = DrawPort().Width();
     int w = r.Width() * sizeof(tColor);
     tColor *ps = NULL;
     tColor *pd = data + wd * r.Top() + r.Left();
     for (int y = r.Height(); y-- > 0; ) {
         if (ps)
            memcpy(pd, ps, w); // all other lines are copied fast from the first one
         else {
            // explicitly fill the first line:
            tColor *cd = ps = pd;
            for (int x = r.Width(); x-- > 0; ) {
                *cd = Color;
                cd++;
                }
            }
         pd += wd;
         }
     MarkDrawPortDirty(r);
     }
  Unlock();
}

void cPixmapMemory::DrawEllipse(const cRect &Rect, tColor Color, int Quadrants)
{
//TODO use anti-aliasing?
//TODO fix alignment
  Lock();
  // Algorithm based on http://homepage.smc.edu/kennedy_john/BELIPSE.PDF
  int x1 = Rect.Left();
  int y1 = Rect.Top();
  int x2 = Rect.Right();
  int y2 = Rect.Bottom();
  int rx = x2 - x1;
  int ry = y2 - y1;
  int cx = (x1 + x2) / 2;
  int cy = (y1 + y2) / 2;
  switch (abs(Quadrants)) {
    case 0: rx /= 2; ry /= 2; break;
    case 1: cx = x1; cy = y2; break;
    case 2: cx = x2; cy = y2; break;
    case 3: cx = x2; cy = y1; break;
    case 4: cx = x1; cy = y1; break;
    case 5: cx = x1;          ry /= 2; break;
    case 6:          cy = y2; rx /= 2; break;
    case 7: cx = x2;          ry /= 2; break;
    case 8:          cy = y1; rx /= 2; break;
    default: ;
    }
  int TwoASquare = 2 * rx * rx;
  int TwoBSquare = 2 * ry * ry;
  int x = rx;
  int y = 0;
  int XChange = ry * ry * (1 - 2 * rx);
  int YChange = rx * rx;
  int EllipseError = 0;
  int StoppingX = TwoBSquare * rx;
  int StoppingY = 0;
  while (StoppingX >= StoppingY) {
        switch (Quadrants) {
          case  5: DrawRectangle(cRect(cx,     cy + y, x + 1, 1), Color); // no break
          case  1: DrawRectangle(cRect(cx,     cy - y, x + 1, 1), Color); break;
          case  7: DrawRectangle(cRect(cx - x, cy + y, x + 1, 1), Color); // no break
          case  2: DrawRectangle(cRect(cx - x, cy - y, x + 1, 1), Color); break;
          case  3: DrawRectangle(cRect(cx - x, cy + y, x + 1, 1), Color); break;
          case  4: DrawRectangle(cRect(cx,     cy + y, x + 1, 1), Color); break;
          case  0:
          case  6: DrawRectangle(cRect(cx - x, cy - y, 2 * x + 1,       1), Color); if (Quadrants == 6) break;
          case  8: DrawRectangle(cRect(cx - x, cy + y, 2 * x + 1,       1), Color); break;
          case -1: DrawRectangle(cRect(cx + x, cy - y, rx - x + 1,      1), Color); break;
          case -2: DrawRectangle(cRect(x1,     cy - y, cx - x - x1 + 1, 1), Color); break;
          case -3: DrawRectangle(cRect(x1,     cy + y, cx - x - x1 + 1, 1), Color); break;
          case -4: DrawRectangle(cRect(cx + x, cy + y, rx - x + 1,      1), Color); break;
          default: ;
          }
        y++;
        StoppingY += TwoASquare;
        EllipseError += YChange;
        YChange += TwoASquare;
        if (2 * EllipseError + XChange > 0) {
           x--;
           StoppingX -= TwoBSquare;
           EllipseError += XChange;
           XChange += TwoBSquare;
           }
        }
  x = 0;
  y = ry;
  XChange = ry * ry;
  YChange = rx * rx * (1 - 2 * ry);
  EllipseError = 0;
  StoppingX = 0;
  StoppingY = TwoASquare * ry;
  while (StoppingX <= StoppingY) {
        switch (Quadrants) {
          case  5: DrawRectangle(cRect(cx,     cy + y, x + 1, 1), Color); // no break
          case  1: DrawRectangle(cRect(cx,     cy - y, x + 1, 1), Color); break;
          case  7: DrawRectangle(cRect(cx - x, cy + y, x + 1, 1), Color); // no break
          case  2: DrawRectangle(cRect(cx - x, cy - y, x + 1, 1), Color); break;
          case  3: DrawRectangle(cRect(cx - x, cy + y, x + 1, 1), Color); break;
          case  4: DrawRectangle(cRect(cx,     cy + y, x + 1, 1), Color); break;
          case  0:
          case  6: DrawRectangle(cRect(cx - x, cy - y, 2 * x + 1,       1), Color); if (Quadrants == 6) break;
          case  8: DrawRectangle(cRect(cx - x, cy + y, 2 * x + 1,       1), Color); break;
          case -1: DrawRectangle(cRect(cx + x, cy - y, rx - x + 1,      1), Color); break;
          case -2: DrawRectangle(cRect(x1,     cy - y, cx - x - x1 + 1, 1), Color); break;
          case -3: DrawRectangle(cRect(x1,     cy + y, cx - x - x1 + 1, 1), Color); break;
          case -4: DrawRectangle(cRect(cx + x, cy + y, rx - x + 1,      1), Color); break;
          default: ;
          }
        x++;
        StoppingX += TwoBSquare;
        EllipseError += XChange;
        XChange += TwoBSquare;
        if (2 * EllipseError + YChange > 0) {
           y--;
           StoppingY -= TwoASquare;
           EllipseError += YChange;
           YChange += TwoASquare;
           }
        }
  MarkDrawPortDirty(Rect);
  Unlock();
}

void cPixmapMemory::DrawSlope(const cRect &Rect, tColor Color, int Type)
{
  //TODO anti-aliasing?
  //TODO also simplify cBitmap::DrawSlope()
  Lock();
  bool upper    = Type & 0x01;
  bool falling  = Type & 0x02;
  bool vertical = Type & 0x04;
  int x1 = Rect.Left();
  int y1 = Rect.Top();
  int x2 = Rect.Right();
  int y2 = Rect.Bottom();
  int w  = Rect.Width();
  int h  = Rect.Height();
  if (vertical) {
     for (int y = y1; y <= y2; y++) {
         double c = cos((y - y1) * M_PI / h);
         if (falling)
            c = -c;
         int x = (x1 + x2) / 2 + int(w * c / 2);
         if (upper && !falling || !upper && falling)
            DrawRectangle(cRect(x1, y, x - x1 + 1, 1), Color);
         else
            DrawRectangle(cRect(x, y, x2 - x + 1, 1), Color);
         }
     }
  else {
     for (int x = x1; x <= x2; x++) {
         double c = cos((x - x1) * M_PI / w);
         if (falling)
            c = -c;
         int y = (y1 + y2) / 2 + int(h * c / 2);
         if (upper)
            DrawRectangle(cRect(x, y1, 1, y - y1 + 1), Color);
         else
            DrawRectangle(cRect(x, y, 1, y2 - y + 1), Color);
         }
     }
  MarkDrawPortDirty(Rect);
  Unlock();
}

void cPixmapMemory::Render(const cPixmap *Pixmap, const cRect &Source, const cPoint &Dest)
{
  Lock();
  if (Pixmap->Alpha() != ALPHA_TRANSPARENT) {
     if (const cPixmapMemory *pm = dynamic_cast<const cPixmapMemory *>(Pixmap)) {
        cRect s = Source.Intersected(Pixmap->DrawPort().Size());
        if (!s.IsEmpty()) {
           cPoint v = Dest - Source.Point();
           cRect d = s.Shifted(v).Intersected(DrawPort().Size());
           if (!d.IsEmpty()) {
              s = d.Shifted(-v);
              int a = pm->Alpha();
              int ws = pm->DrawPort().Width();
              int wd = DrawPort().Width();
              const tColor *ps = pm->data + ws * s.Top() + s.Left();
              tColor *pd = data + wd * d.Top() + d.Left();
              for (int y = d.Height(); y-- > 0; ) {
                  const tColor *cs = ps;
                  tColor *cd = pd;
                  for (int x = d.Width(); x-- > 0; ) {
                      *cd = AlphaBlend(*cs, *cd, a);
                      cs++;
                      cd++;
                      }
                  ps += ws;
                  pd += wd;
                  }
              MarkDrawPortDirty(d);
              }
           }
        }
     }
  Unlock();
}

void cPixmapMemory::Copy(const cPixmap *Pixmap, const cRect &Source, const cPoint &Dest)
{
  Lock();
  if (const cPixmapMemory *pm = dynamic_cast<const cPixmapMemory *>(Pixmap)) {
     cRect s = Source.Intersected(pm->DrawPort().Size());
     if (!s.IsEmpty()) {
        cPoint v = Dest - Source.Point();
        cRect d = s.Shifted(v).Intersected(DrawPort().Size());
        if (!d.IsEmpty()) {
           s = d.Shifted(-v);
           int ws = pm->DrawPort().Width();
           int wd = DrawPort().Width();
           int w = d.Width() * sizeof(tColor);
           const tColor *ps = pm->data + ws * s.Top() + s.Left();
           tColor *pd = data + wd * d.Top() + d.Left();
           for (int y = d.Height(); y-- > 0; ) {
               memcpy(pd, ps, w);
               ps += ws;
               pd += wd;
               }
           MarkDrawPortDirty(d);
           }
        }
     }
  Unlock();
}

void cPixmapMemory::Scroll(const cPoint &Dest, const cRect &Source)
{
  Lock();
  cRect s;
  if (&Source == &cRect::Null)
     s = DrawPort().Shifted(-DrawPort().Point());
  else
     s = Source.Intersected(DrawPort().Size());
  if (!s.IsEmpty()) {
     cPoint v = Dest - Source.Point();
     cRect d = s.Shifted(v).Intersected(DrawPort().Size());
     if (!d.IsEmpty()) {
        s = d.Shifted(-v);
        if (d.Point() != s.Point()) {
           int ws = DrawPort().Width();
           int wd = ws;
           int w = d.Width() * sizeof(tColor);
           const tColor *ps = data + ws * s.Top() + s.Left();
           tColor *pd = data + wd * d.Top() + d.Left();
           for (int y = d.Height(); y-- > 0; ) {
               memmove(pd, ps, w); // source and destination might overlap!
               ps += ws;
               pd += wd;
               }
           if (panning)
              SetDrawPortPoint(DrawPort().Point().Shifted(s.Point() - d.Point()), false);
           else
              MarkDrawPortDirty(d);
           }
        }
     }
  Unlock();
}

void cPixmapMemory::Pan(const cPoint &Dest, const cRect &Source)
{
  Lock();
  panning = true;
  Scroll(Dest, Source);
  panning = false;
  Unlock();
}

// --- cOsd ------------------------------------------------------------------

static const char *OsdErrorTexts[] = {
  "ok",
  "too many areas",
  "too many colors",
  "bpp not supported",
  "areas overlap",
  "wrong alignment",
  "out of memory",
  "wrong area size",
  "unknown",
  };

int cOsd::osdLeft = 0;
int cOsd::osdTop = 0;
int cOsd::osdWidth = 0;
int cOsd::osdHeight = 0;
cVector<cOsd *> cOsd::Osds;
cMutex cOsd::mutex;

cOsd::cOsd(int Left, int Top, uint Level)
{
  cMutexLock MutexLock(&mutex);
  isTrueColor = false;
  savedBitmap = NULL;
  numBitmaps = 0;
  savedPixmap = NULL;
  left = Left;
  top = Top;
  width = height = 0;
  level = Level;
  active = false;
  for (int i = 0; i < Osds.Size(); i++) {
      if (Osds[i]->level > level) {
         Osds.Insert(this, i);
         return;
         }
      }
  Osds.Append(this);
}

cOsd::~cOsd()
{
  cMutexLock MutexLock(&mutex);
  for (int i = 0; i < numBitmaps; i++)
      delete bitmaps[i];
  delete savedBitmap;
  delete savedPixmap;
  for (int i = 0; i < pixmaps.Size(); i++)
      delete pixmaps[i];
  for (int i = 0; i < Osds.Size(); i++) {
      if (Osds[i] == this) {
         Osds.Remove(i);
         if (Osds.Size())
            Osds[0]->SetActive(true);
         break;
         }
      }
}

void cOsd::SetOsdPosition(int Left, int Top, int Width, int Height)
{
  osdLeft = Left;
  osdTop = Top;
  osdWidth = constrain(Width, MINOSDWIDTH, MAXOSDWIDTH);
  osdHeight = constrain(Height, MINOSDHEIGHT, MAXOSDHEIGHT);
}

void cOsd::SetAntiAliasGranularity(uint FixedColors, uint BlendColors)
{
  if (isTrueColor)
     return;
  for (int i = 0; i < numBitmaps; i++)
      bitmaps[i]->SetAntiAliasGranularity(FixedColors, BlendColors);
}

cBitmap *cOsd::GetBitmap(int Area)
{
  return Area < numBitmaps ? (isTrueColor ? bitmaps[0] : bitmaps[Area]) : NULL;
}

cPixmap *cOsd::CreatePixmap(int Layer, const cRect &ViewPort, const cRect &DrawPort)
{
  if (isTrueColor) {
     LOCK_PIXMAPS;
     cPixmap *Pixmap = new cPixmapMemory(Layer, ViewPort, DrawPort);
     if (AddPixmap(Pixmap))
        return Pixmap;
     delete Pixmap;
     }
  return NULL;
}

void cOsd::DestroyPixmap(cPixmap *Pixmap)
{
  if (Pixmap) {
     LOCK_PIXMAPS;
     for (int i = 1; i < pixmaps.Size(); i++) { // begin at 1 - don't let the background pixmap be destroyed!
         if (pixmaps[i] == Pixmap) {
            pixmaps[0]->MarkViewPortDirty(Pixmap->ViewPort());
            delete Pixmap;
            pixmaps[i] = NULL;
            return;
            }
         }
     esyslog("ERROR: attempt to destroy an unregistered pixmap");
     }
}

cPixmap *cOsd::AddPixmap(cPixmap *Pixmap)
{
  if (Pixmap) {
     LOCK_PIXMAPS;
     for (int i = 0; i < pixmaps.Size(); i++) {
         if (!pixmaps[i])
            return pixmaps[i] = Pixmap;
         }
     pixmaps.Append(Pixmap);
     }
  return Pixmap;
}

cPixmapMemory *cOsd::RenderPixmaps(void)
{
  cPixmapMemory *Pixmap = NULL;
  if (isTrueColor) {
     LOCK_PIXMAPS;
     // Collect overlapping dirty rectangles:
     cRect d;
     for (int i = 0; i < pixmaps.Size(); i++) {
         if (cPixmap *pm = pixmaps[i]) {
            if (!pm->DirtyViewPort().IsEmpty()) {
               if (d.IsEmpty() || d.Intersects(pm->DirtyViewPort())) {
                  d.Combine(pm->DirtyViewPort());
                  pm->SetClean();
                  }
               }
            }
         }
     if (!d.IsEmpty()) {
//#define DebugDirty
#ifdef DebugDirty
        static cRect OldDirty;
        cRect NewDirty = d;
        d.Combine(OldDirty);
        OldDirty = NewDirty;
#endif
        Pixmap = new cPixmapMemory(0, d);
        Pixmap->Clear();
        // Render the individual pixmaps into the resulting pixmap:
        for (int Layer = 0; Layer < MAXPIXMAPLAYERS; Layer++) {
            for (int i = 0; i < pixmaps.Size(); i++) {
                if (cPixmap *pm = pixmaps[i]) {
                   if (pm->Layer() == Layer)
                   Pixmap->DrawPixmap(pm, d);
                   }
                }
            }
#ifdef DebugDirty
        cPixmapMemory DirtyIndicator(7, NewDirty);
        static tColor DirtyIndicatorColors[] = { 0x7FFFFF00, 0x7F00FFFF };
        static int DirtyIndicatorIndex = 0;
        DirtyIndicator.Fill(DirtyIndicatorColors[DirtyIndicatorIndex]);
        DirtyIndicatorIndex = 1 - DirtyIndicatorIndex;
        Pixmap->Render(&DirtyIndicator, DirtyIndicator.DrawPort(), DirtyIndicator.ViewPort().Point().Shifted(-Pixmap->ViewPort().Point()));
#endif
        }
     }
  return Pixmap;
}

eOsdError cOsd::CanHandleAreas(const tArea *Areas, int NumAreas)
{
  if (NumAreas > MAXOSDAREAS)
     return oeTooManyAreas;
  eOsdError Result = oeOk;
  for (int i = 0; i < NumAreas; i++) {
      if (Areas[i].x1 > Areas[i].x2 || Areas[i].y1 > Areas[i].y2 || Areas[i].x1 < 0 || Areas[i].y1 < 0)
         return oeWrongAlignment;
      for (int j = i + 1; j < NumAreas; j++) {
          if (Areas[i].Intersects(Areas[j])) {
             Result = oeAreasOverlap;
             break;
             }
          }
      if (Areas[i].bpp == 32) {
         if (NumAreas > 1)
            return oeTooManyAreas;
         }
      }
  return Result;
}

eOsdError cOsd::SetAreas(const tArea *Areas, int NumAreas)
{
  eOsdError Result = CanHandleAreas(Areas, NumAreas);
  if (Result == oeOk) {
     while (numBitmaps)
           delete bitmaps[--numBitmaps];
     for (int i = 0; i < pixmaps.Size(); i++) {
         delete pixmaps[i];
         pixmaps[i] = NULL;
         }
     width = height = 0;
     isTrueColor = NumAreas == 1 && Areas[0].bpp == 32;
     if (isTrueColor) {
        width = Areas[0].x2 - Areas[0].x1 + 1;
        height = Areas[0].y2 - Areas[0].y1 + 1;
        cPixmap *Pixmap = CreatePixmap(0, cRect(Areas[0].x1, Areas[0].y1, width, height));
        Pixmap->Clear();
        bitmaps[numBitmaps++] = new cBitmap(10, 10, 8); // dummy bitmap for GetBitmap()
        }
     else {
        for (int i = 0; i < NumAreas; i++) {
            bitmaps[numBitmaps++] = new cBitmap(Areas[i].Width(), Areas[i].Height(), Areas[i].bpp, Areas[i].x1, Areas[i].y1);
            width = max(width, Areas[i].x2 + 1);
            height = max(height, Areas[i].y2 + 1);
            }
        }
     }
  else
     esyslog("ERROR: cOsd::SetAreas returned %d (%s)", Result, Result < oeUnknown ? OsdErrorTexts[Result] : OsdErrorTexts[oeUnknown]);
  return Result;
}

void cOsd::SaveRegion(int x1, int y1, int x2, int y2)
{
  if (isTrueColor) {
     delete savedPixmap;
     cRect r(x1, y1, x2 - x1 + 1, y2 - y1 + 1);
     savedPixmap = new cPixmapMemory(0, r);
     savedPixmap->Copy(pixmaps[0], r, cPoint(0, 0));
     }
  else {
     delete savedBitmap;
     savedBitmap = new cBitmap(x2 - x1 + 1, y2 - y1 + 1, 8, x1, y1);
     for (int i = 0; i < numBitmaps; i++)
         savedBitmap->DrawBitmap(bitmaps[i]->X0(), bitmaps[i]->Y0(), *bitmaps[i]);
     }
}

void cOsd::RestoreRegion(void)
{
  if (isTrueColor) {
     if (savedPixmap) {
        pixmaps[0]->Copy(savedPixmap, savedPixmap->DrawPort(), savedPixmap->ViewPort().Point());
        delete savedPixmap;
        savedPixmap = NULL;
        }
     }
  else {
     if (savedBitmap) {
        DrawBitmap(savedBitmap->X0(), savedBitmap->Y0(), *savedBitmap);
        delete savedBitmap;
        savedBitmap = NULL;
        }
     }
}

eOsdError cOsd::SetPalette(const cPalette &Palette, int Area)
{
  if (isTrueColor)
     return oeOk;
  if (Area < numBitmaps) {
     bitmaps[Area]->Take(Palette);
     return oeOk;
     }
  return oeUnknown;
}

void cOsd::DrawImage(const cPoint &Point, const cImage &Image)
{
  if (isTrueColor)
     pixmaps[0]->DrawImage(Point, Image);
}

void cOsd::DrawImage(const cPoint &Point, int ImageHandle)
{
  if (isTrueColor)
     pixmaps[0]->DrawImage(Point, ImageHandle);
}

void cOsd::DrawPixel(int x, int y, tColor Color)
{
  if (isTrueColor)
     pixmaps[0]->DrawPixel(cPoint(x, y), Color);
  else {
     for (int i = 0; i < numBitmaps; i++)
         bitmaps[i]->DrawPixel(x, y, Color);
     }
}

void cOsd::DrawBitmap(int x, int y, const cBitmap &Bitmap, tColor ColorFg, tColor ColorBg, bool ReplacePalette, bool Overlay)
{
  if (isTrueColor)
     pixmaps[0]->DrawBitmap(cPoint(x, y), Bitmap, ColorFg, ColorBg, Overlay);
  else {
     for (int i = 0; i < numBitmaps; i++)
         bitmaps[i]->DrawBitmap(x, y, Bitmap, ColorFg, ColorBg, ReplacePalette, Overlay);
     }
}

void cOsd::DrawText(int x, int y, const char *s, tColor ColorFg, tColor ColorBg, const cFont *Font, int Width, int Height, int Alignment)
{
  if (isTrueColor)
     pixmaps[0]->DrawText(cPoint(x, y), s, ColorFg, ColorBg, Font, Width, Height, Alignment);
  else {
     for (int i = 0; i < numBitmaps; i++)
         bitmaps[i]->DrawText(x, y, s, ColorFg, ColorBg, Font, Width, Height, Alignment);
     }
}

void cOsd::DrawRectangle(int x1, int y1, int x2, int y2, tColor Color)
{
  if (isTrueColor)
     pixmaps[0]->DrawRectangle(cRect(x1, y1, x2 - x1 + 1, y2 - y1 + 1), Color);
  else {
     for (int i = 0; i < numBitmaps; i++)
         bitmaps[i]->DrawRectangle(x1, y1, x2, y2, Color);
     }
}

void cOsd::DrawEllipse(int x1, int y1, int x2, int y2, tColor Color, int Quadrants)
{
  if (isTrueColor)
     pixmaps[0]->DrawEllipse(cRect(x1, y1, x2 - x1 + 1, y2 - y1 + 1), Color, Quadrants);
  else {
     for (int i = 0; i < numBitmaps; i++)
         bitmaps[i]->DrawEllipse(x1, y1, x2, y2, Color, Quadrants);
     }
}

void cOsd::DrawSlope(int x1, int y1, int x2, int y2, tColor Color, int Type)
{
  if (isTrueColor)
     pixmaps[0]->DrawSlope(cRect(x1, y1, x2 - x1 + 1, y2 - y1 + 1), Color, Type);
  else {
     for (int i = 0; i < numBitmaps; i++)
         bitmaps[i]->DrawSlope(x1, y1, x2, y2, Color, Type);
     }
}

void cOsd::Flush(void)
{
}

// --- cOsdProvider ----------------------------------------------------------

cOsdProvider *cOsdProvider::osdProvider = NULL;
int cOsdProvider::oldWidth = 0;
int cOsdProvider::oldHeight = 0;
double cOsdProvider::oldAspect = 1.0;
cImage *cOsdProvider::images[MAXOSDIMAGES] = { NULL };

cOsdProvider::cOsdProvider(void)
{
  delete osdProvider;
  osdProvider = this;
}

cOsdProvider::~cOsdProvider()
{
  osdProvider = NULL;
}

cOsd *cOsdProvider::NewOsd(int Left, int Top, uint Level)
{
  cMutexLock MutexLock(&cOsd::mutex);
  if (Level == OSD_LEVEL_DEFAULT && cOsd::IsOpen())
     esyslog("ERROR: attempt to open OSD while it is already open - using dummy OSD!");
  else if (osdProvider) {
     cOsd *ActiveOsd = cOsd::Osds.Size() ? cOsd::Osds[0] : NULL;
     cOsd *Osd = osdProvider->CreateOsd(Left, Top, Level);
     if (Osd == cOsd::Osds[0]) {
        if (ActiveOsd)
           ActiveOsd->SetActive(false);
        Osd->SetActive(true);
        }
     return Osd;
     }
  else
     esyslog("ERROR: no OSD provider available - using dummy OSD!");
  return new cOsd(Left, Top, 999); // create a dummy cOsd, so that access won't result in a segfault
}

void cOsdProvider::UpdateOsdSize(bool Force)
{
  int Width;
  int Height;
  double Aspect;
  cDevice::PrimaryDevice()->GetOsdSize(Width, Height, Aspect);
  if (Width != oldWidth || Height != oldHeight || !DoubleEqual(Aspect, oldAspect) || Force) {
     Setup.OSDLeft = int(round(Width * Setup.OSDLeftP));
     Setup.OSDTop = int(round(Height * Setup.OSDTopP));
     Setup.OSDWidth = int(round(Width * Setup.OSDWidthP)) & ~0x07; // OSD width must be a multiple of 8
     Setup.OSDHeight = int(round(Height * Setup.OSDHeightP));
     Setup.OSDAspect = Aspect;
     Setup.FontOsdSize = int(round(Height * Setup.FontOsdSizeP));
     Setup.FontFixSize = int(round(Height * Setup.FontFixSizeP));
     Setup.FontSmlSize = int(round(Height * Setup.FontSmlSizeP));
     cFont::SetFont(fontOsd, Setup.FontOsd, Setup.FontOsdSize);
     cFont::SetFont(fontFix, Setup.FontFix, Setup.FontFixSize);
     cFont::SetFont(fontSml, Setup.FontSml, min(Setup.FontSmlSize, Setup.FontOsdSize));
     oldWidth = Width;
     oldHeight = Height;
     oldAspect = Aspect;
     dsyslog("OSD size changed to %dx%d @ %g", Width, Height, Aspect);
     }
}

bool cOsdProvider::SupportsTrueColor(void)
{
  if (osdProvider) {
     return osdProvider->ProvidesTrueColor();
     }
  else
     esyslog("ERROR: no OSD provider available in call to SupportsTrueColor()");
  return false;
}

int cOsdProvider::StoreImageData(const cImage &Image)
{
  LOCK_PIXMAPS;
  for (int i = 1; i < MAXOSDIMAGES; i++) {
      if (!images[i]) {
         images[i] = new cImage(Image);
         return i;
         }
      }
  return 0;
}

void cOsdProvider::DropImageData(int ImageHandle)
{
  LOCK_PIXMAPS;
  if (0 < ImageHandle && ImageHandle < MAXOSDIMAGES) {
     delete images[ImageHandle];
     images[ImageHandle] = NULL;
     }
}

const cImage *cOsdProvider::GetImageData(int ImageHandle)
{
  LOCK_PIXMAPS;
  if (0 < ImageHandle && ImageHandle < MAXOSDIMAGES)
     return images[ImageHandle];
  return NULL;
}

int cOsdProvider::StoreImage(const cImage &Image)
{
  if (osdProvider)
     return osdProvider->StoreImageData(Image);
  return 0;
}

void cOsdProvider::DropImage(int ImageHandle)
{
  if (osdProvider)
     osdProvider->DropImageData(ImageHandle);
}

void cOsdProvider::Shutdown(void)
{
  delete osdProvider;
  osdProvider = NULL;
}

// --- cTextScroller ---------------------------------------------------------

cTextScroller::cTextScroller(void)
{
  osd = NULL;
  left = top = width = height = 0;
  font = NULL;
  colorFg = 0;
  colorBg = 0;
  offset = 0;
  shown = 0;
}

cTextScroller::cTextScroller(cOsd *Osd, int Left, int Top, int Width, int Height, const char *Text, const cFont *Font, tColor ColorFg, tColor ColorBg)
{
  Set(Osd, Left, Top, Width, Height, Text, Font, ColorFg, ColorBg);
}

void cTextScroller::Set(cOsd *Osd, int Left, int Top, int Width, int Height, const char *Text, const cFont *Font, tColor ColorFg, tColor ColorBg)
{
  osd = Osd;
  left = Left;
  top = Top;
  width = Width;
  height = Height;
  font = Font;
  colorFg = ColorFg;
  colorBg = ColorBg;
  offset = 0;
  textWrapper.Set(Text, Font, Width);
  shown = min(Total(), height / font->Height());
  height = shown * font->Height(); // sets height to the actually used height, which may be less than Height
  DrawText();
}

void cTextScroller::Reset(void)
{
  osd = NULL; // just makes sure it won't draw anything
}

void cTextScroller::DrawText(void)
{
  if (osd) {
     for (int i = 0; i < shown; i++)
         osd->DrawText(left, top + i * font->Height(), textWrapper.GetLine(offset + i), colorFg, colorBg, font, width);
     }
}

void cTextScroller::Scroll(bool Up, bool Page)
{
  if (Up) {
     if (CanScrollUp()) {
        offset -= Page ? shown : 1;
        if (offset < 0)
           offset = 0;
        DrawText();
        }
     }
  else {
     if (CanScrollDown()) {
        offset += Page ? shown : 1;
        if (offset + shown > Total())
           offset = Total() - shown;
        DrawText();
        }
     }
}