vdr/osd.c
Klaus Schmidinger 14bd32b948 Version 1.7.37
VDR developer version 1.7.37 is now available at

       ftp://ftp.tvdr.de/vdr/Developer/vdr-1.7.37.tar.bz2

A 'diff' against the previous version is available at

       ftp://ftp.tvdr.de/vdr/Developer/vdr-1.7.36-1.7.37.diff

MD5 checksums:

602dc7e678bcfcf075da36344a337562  vdr-1.7.37.tar.bz2
34e953fcffc112f316cbfc1f53915324  vdr-1.7.36-1.7.37.diff

WARNING:
========

This is a developer version. Even though I use it in my productive
environment. I strongly recommend that you only use it under controlled
conditions and for testing and debugging.

Approaching version 2.0.0:
==========================

If all goes well, there should be no more functional or API changes
before the final version 2.0.0. There will just be a few more fixes.

From the HISTORY file:
- Now also using FindHeader() in cMpeg2Fixer::AdjTref() (pointed out by Sören Moch).
- Added missing template for DVBDIR to Make.config.template (reported by Derek Kelly).
- The LCARS menu now also works if the OSD has only 1bpp (two colors).
- Fixed possible garbage in the remaining time of the LCARS replay display in case the
  hours change from two to one digit.
- Fixed upscaling bitmaps. The last row and column of the scaled bitmap was not filled,
  which resulted in empty lines between scaled subtitles.
- Fixed a leftover line in case a two line subtitle was followed by a one line
  subtitle on the dvbhddevice in "high level" OSD mode.
- Returning 0 from cDvbSdFfDevice::NumProvidedSystems() if option --outputonly is given.
- The index file is now closed after initially reading it if it is older than 3600 seconds.
- Improved responsiveness during replay when close to the recording's end.
- Fixed a leftover progress display in the LCARS main menu when replay of a recording
  ends while the menu is open, and the live channel has no EPG information.
- Fixed possible audio chatter when a recording is replayed to its very end.
- Added dependency on 'i18n' to 'install-i18n' in the VDR Makefile (thanks to Tobias
  Grimm).
- Changed several calls to Skins.Message() in vdr.c to Skins.QueueMessage() in order to
  avoid a black screen while such a message is displayed in case the channel will be
  switched (reported by Uwe Scheffler).
- Updated the Slovakian language texts (thanks to Milan Hrala).
- Improved LIRC timing for repeat function.
- When pausing live video, the current audio and subtitle tracks are now retained.
- Added some notes about plugin Makefiles to PLUGINS.html.
- Avoiding an extra key press event if the repeat function kicks in when controlling
  VDR via the PC keyboard.
- The new options "Setup/Miscellaneous/Remote control repeat delay" and
  "Setup/Miscellaneous/Remote control repeat delta" can be used to adjust the
  behavior of the remote control in case a key is held pressed down for a while, so
  that the repeat function kicks in (see MANUAL).
  The builtin LIRC and KBD remote controls already use these parameters. It is
  recommended that plugins that implement an interface to any kind of remote controls
  also use the parameters Setup.RcRepeatDelay and Setup.RcRepeatDelta for the desired
  purpose, and remove any setup options they might have that serve the same purpose.
- cTimer no longer does any special "VFAT" handling to shorten directory names to 40
  characters. When a string is used as a directory name for a recording, the maximum
  length of the directory path, as well as the individual directory names, is now
  limited to the values specified by the new command line option --dirnames (see
  man vdr(1) for details). For backwards compatibility the option --vfat is still
  available and has the same effect as --dirnames=250,40,1.
- The macro MaxFileName is now obsolete and may be removed in future versions. Use
  NAME_MAX directly instead.
- There is no more fixed limit to the maximum number of cPixmap objects an OSD can
  create. However, a particular device may still be unable to create an arbitrary
  number of pixmaps, due to limited resources. So it's always a good idea to use
  as few pixmaps as possible.
- Fixed formatting and removed some superfluous break statements in vdr.c's command
  line option switch.
2013-02-09 18:56:41 +01:00

2138 lines
62 KiB
C

/*
* 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.35 2013/02/08 10:16:47 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 (isTrueColor) {
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];
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 -1;
}
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();
}
}
}