vdr/osd.c
Klaus Schmidinger f82cdfed40 Version 2.1.10
VDR developer version 2.1.10 is now available at

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

A 'diff' against the previous version is available at

       ftp://ftp.tvdr.de/vdr/Developer/vdr-2.1.9-2.1.10.diff

MD5 checksums:

889f053463e2720f1ad6517cc19e82f6  vdr-2.1.10.tar.bz2
c126fb89a79db55867ae9d9a869569aa  vdr-2.1.9-2.1.10.diff

Approaching version 2.2.0:
==========================

I have decided to release one more developer version because there were
complaints about missing functionality from the "jumpingseconds" and
"binary skip" patches regarding the Green/Yellow and Prev/Next keys, and
I didn't want to cause another "patch frenzy" in this newly refurbished
area of VDR. The other (more important) reason for this additional
developer release was the change in the return value of cOsd::RenderPixmaps(),
which is very important for plugins that implement their own version of
cPixmap, e.g. for use with fast graphics hardware.

If there are no more serious bug reports, the final version 2.2.0 of VDR
shall be released on February 19, 2015, which marks the 15th anniversary
of VDR.
So please test this developer version intensely and report any problems
you might encounter as soon as possible.

The following language files still have the given number of untranslated texts:

ar.po: 54
ca_ES.po: 54
cs_CZ.po: 54
da_DK.po: 187
el_GR.po: 250
es_ES.po: 54
et_EE.po: 3
fi_FI.po: 3
hr_HR.po: 187
hu_HU.po: 4
it_IT.po: 3
lt_LT.po: 3
mk_MK.po: 3
nn_NO.po: 315
pl_PL.po: 54
pt_PT.po: 82
ro_RO.po: 3
ru_RU.po: 54
sk_SK.po: 54
sl_SI.po: 55
sr_RS.po: 54
sv_SE.po: 54
tr_TR.po: 187
uk_UA.po: 3
zh_CN.po: 54

If nobody takes care of these, they will remain untranslated in version 2.2.0.

DEADLINE FOR SUBMITTING TRANSLATIONS IS WEDNESDAY, FEBRUARY 18!

From the HISTORY file:
- Updated the Finnish OSD texts (thanks to Rolf Ahrenberg).
- Updated the Macedonian OSD texts (thanks to Dimitar Petrovski).
- Updated the Romanian OSD texts (thanks to Lucian Muresan).
- Updated the Ukrainian OSD texts (thanks to Yarema Aka Knedlyk).
- Added "#REMOTE=LIRC" to Make.config.template (suggested by Martin Dauskardt).
- Updated the Estonian OSD texts (thanks to Arthur Konovalov).
- Increased the width of the left text column in the Setup menus to avoid cutting
  off German texts.
- Added an empty target to the Makefile to make sure the sub-make for libsi is always
  called (thanks to Sören Moch).
- Updated the Italian OSD texts (thanks to Diego Pierotto and Nino Gerbino).
- Updated the Lithuanian OSD texts (thanks to Valdemaras Pipiras).
- The new options "Setup/Replay/Skip distance with Green/Yellow keys" and
  "Setup/Replay/Skip distance with Green/Yellow keys in repeat" can be used to
  configure the number of seconds to skip when pressing these keys once or pressing
  and holding them (based on a patch from Matthias Senzel, originally from Tobias
  Faust).
- The new option "Setup/Replay/Use Prev/Next keys for adaptive skipping" can be used
  to change the behavior of these keys during replay. They normally jump between
  editing marks, but with this option set to 'yes' they will do adaptive skipping.
- The new command line option --chartab can be used to set the default character
  table to use for strings in the DVB data stream that don't begin with a proper
  character table indicator (suggested by Christopher Reimer). The old mechanism
  of using the environment variable VDR_CHARSET_OVERRIDE still works, but is now
  deprecated and may be removed in a future version. The value given in the --chartab
  option takes precedence over that in VDR_CHARSET_OVERRIDE.
- cOsd::RenderPixmaps() now returns a pointer to cPixmap instead of cPixmapMemory
  (suggested by Thomas Reufer). This is necessary to allow plugins with derived
  cPixmap implementations to use this function. Plugins that use this function
  with cPixmapMemory now need to add a dynamic cast to the call, as in

    cPixmapMemory *pm = dynamic_cast<cPixmapMemory *>(RenderPixmaps()));

  They also need to call DestroyPixmap(pm) instead of "delete pm" to properly release
  the resulting pixmap after use.
  The dvbhddevice plugin has been modified accordingly.
- A cPixmap with a negative layer no longer marks any portion of the OSD's view port
  as "dirty" when drawing on it. This may improve performance when drawing on a
  hidden pixmap, because it avoids unnecessary refreshes of the OSD.
- Added a missing initialization of "panning" to the constructor of cPixmapMemory.
- Updated the Dutch OSD texts (thanks to Erik Oomen).
- Updated the French OSD texts (thanks to Régis Bossut).
- Fixed the German translation of "Zap timeout".
2015-02-11 21:42:20 +01:00

2186 lines
64 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 3.5 2015/02/11 09:48:02 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::Fill(tIndex Index)
{
if (bitmap) {
memset(bitmap, Index, width * height);
dirtyX1 = 0;
dirtyY1 = 0;
dirtyX2 = width - 1;
dirtyY2 = height - 1;
}
}
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 = max(1, 2 * rx * rx);
int TwoBSquare = max(1, 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) const
{
// 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)
{
if (layer >= 0)
dirtyViewPort.Combine(Rect.Intersected(viewPort));
}
void cPixmap::MarkViewPortDirty(const cPoint &Point)
{
if (layer >= 0 && 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;
}
// The sequence here is important, because the view port is only marked as dirty
// if the layer is >= 0:
if (layer >= 0) {
MarkViewPortDirty(viewPort); // the pixmap is visible and may or may not become invisible
layer = Layer;
}
else if (Layer >= 0) {
layer = Layer;
MarkViewPortDirty(viewPort); // the pixmap was invisible and has become visible
}
else
layer = Layer; // the pixmap was invisible and remains so
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());
panning = false;
}
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 = max(1, 2 * rx * rx);
int TwoBSquare = max(1, 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) {
if (Pixmap->Layer() >= 0)
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;
}
cPixmap *cOsd::RenderPixmaps(void)
{
cPixmap *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 = CreatePixmap(-1, d);
if (Pixmap) {
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::DrawScaledBitmap(int x, int y, const cBitmap &Bitmap, double FactorX, double FactorY, bool AntiAlias)
{
const cBitmap *b = &Bitmap;
if (!DoubleEqual(FactorX, 1.0) || !DoubleEqual(FactorY, 1.0))
b = b->Scaled(FactorX, FactorY, AntiAlias);
DrawBitmap(x, y, *b);
if (b != &Bitmap)
delete b;
}
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 };
int cOsdProvider::osdState = 0;
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;
cMutexLock MutexLock(&cOsd::mutex);
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);
osdState++;
}
}
bool cOsdProvider::OsdSizeChanged(int &State)
{
cMutexLock MutexLock(&cOsd::mutex);
bool Result = osdState != State;
State = osdState;
return Result;
}
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();
}
}
}