vdr/osd.c
Klaus Schmidinger 2db7397542 Version 2.1.7
VDR developer version 2.1.7 is now available at

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

A 'diff' against the previous version is available at

       ftp://ftp.tvdr.de/vdr/Developer/vdr-2.1.6-2.1.7.diff

MD5 checksums:

1c954bad31ce74cd1cbd7987e62d2a98  vdr-2.1.7.tar.bz2
88a90327a75833b7723942d3bd25f954  vdr-2.1.6-2.1.7.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.

From the HISTORY file:
- No longer logging an error message in DirSizeMB() if the given directory doesn't
  exist. This avoids lots of log entries in case several VDRs use the same video
  directory and one of them has already physically removed a recording directory,
  while the others still have it in their list of deleted recordings.
- Updated the Italian OSD texts (thanks to Diego Pierotto).
- A cCamSlot that has WantsTsData set to true in its constructor now also gets
  the CAT and EMM PIDs data.
- Fixed a possible division by zero in frame rate detection.
- VDR now reads command line options from *.conf files in /etc/vdr/conf.d (thanks
  to Lars Hanisch). See vdr.1 and vdr.5 for details.
- Fixed a possible crash in the LCARS skin (thanks to Thomas Reufer).
- Updated the dvbhddevice plugin source.
- Fixed a bug in the Makefile when installing plugins with LCLBLD=1 (thanks to
  Stefan Huelswitt).
- The pid of the PMT in which the CA descriptors of a given channel are broadcast
  is now stored together with the CA descriptors and can be retrieved by calling
  GetPmtPid() (this information is only required to receive encrypted channels
  with the OctopusNet receiver via the 'satip' plugin).
- Channels that are not listed in the SDT are now only marked as OBSOLETE if
  "Setup/DVB/Update channels" is set to a value other than "no" or "PIDs only".
- Fixed multiple OBSOLETE marks in channels that are not listed in the SDT in case
  "Setup/Miscellaneous/Show channel names with source" is set to "yes".
- The new function cOsd::DrawScaledBitmap() is now used for drawing subtitles.
  This function can be reimplemented by high level OSDs which may be able to do
  the scaling in hardware or otherwise more efficiently (thanks to Thomas Reufer).
- Fixed detaching receivers from devices in case a CAM needs to receive the TS
  (reported by Dietmar Spingler).
- Fixed resetting the receiver for EMM pids for CAMs that need to receive the TS
  (reported by Dietmar Spingler).
- Fixed (well, actually worked around) a problem with subtitles not being displayed
  because the broadcaster doesn't set the data's version numbers as required by the
  DVB standard (thanks to Rolf Ahrenberg).
- Fixed support for systemd (thanks to Christopher Reimer).
- Added a missing backslash to the help text of the SVDRP command MOVR (thanks to
  Lars Hanisch).
- Added subsystem id support for DVB devices connected via USB (thanks to Jose
  Alberto Reguero).
- Added the functions IndexOf(), InsertUnique(), AppendUnique() and RemoveElement()
  to the cVector class (thanks to Stefan Schallenberg).
- Fixed a possible out-of-bounds access in cVector::Remove().
- Added functions to set and retrieve the priority of a cReceiver (suggested by
  Frank Schmirler).
- Added the new parameters "Setup/Miscellaneous/Volume steps" and
  ".../Volume linearize" (thanks to Claus Muus). See the MANUAL for details.
- Fixed jumping to an absolute position via the Red key in case replay was paused
  (reported by Dieter Ferdinand).
- Changed the German weekday names from "MonDieMitDonFreSamSon" to
  "Mo.Di.Mi.Do.Fr.Sa.So." (thanks to Stefan Blochberger).
- Now handling CAT sections that consist of more than one TS packet.
- Added handling for DTS audio tracks to cPatPmtParser::ParsePmt() (thanks to
  Thomas Reufer).
- Added support for PGS subtitles (thanks to Thomas Reufer).
- Use of the function cOsd::GetBitmap() outside of derived classes is now deprecated,
  and it may be made 'protected' in a future version, since it doesn't work with
  TrueColor OSDs. Plugin authors may want to modify their code so that it
  works without this function.
- Modified the descriptions of several threads, so that the important information
  (like device or frontend numbers) is within the first 15 characters of the
  string, because only these are displayed in thread listings. Plugin authors may
  want to do the same.
- Added the channel name to log messages that reference a channel (suggested by
  Dietmar Spingler).
- Modified the CAM API so that it is possible to implement CAMs that can be freely
  assigned to any devices (thanks to Jasmin Jessich).
- Plugins can now implement the function SetMenuSortMode() in their skin objects
  derived from cSkinDisplayMenu, to get informed about the currently used sort
  mode, if applicable (suggested by Martin Schirrmacher).
- Added cOsdProvider::OsdSizeChanged(), which plugins that implement an output device
  can call to signal a change in the OSD that requires a redraw of the currently
  displayed object (thanks to Thomas Reufer).
- Added a comment to cRecorder::Activate() about the need to call Detach() in the
  destructor (suggested by Eike Sauer).
- Now returning from removing deleted recordings after at most 10 seconds, or if the
  user presses a remote control key, to keep the system from getting unresponsive
  when removing a huge number of files (reported by Dieter Ferdinand).
- Fixed generating the index file of an existing recording in case at the of a TS file
  there is less data in the buffer than needed by the frame detector. In such a case
  it was possible that frames were missed, and there was most likely a distortion
  when replaying that part of a recording. This is mostly a problem for recordings that
  consist of more than one *.ts file. Single file recordings could only lose some
  frames at their very end, which probably doesn't matter. At any rate, if you have
  generated an index file with VDR version 2.0.6, 2.1.5 or 2.1.6, you may want to
  do so again with this version to make sure the index is OK.
- Added the new command line option --updindex, which can be used to update an
  incomplete index of a recording (based on a patch from Helmut Auer).
2015-01-18 14:45:41 +01:00

2174 lines
63 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.4 2015/01/15 11:20:56 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)
{
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 = 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) {
pixmaps[0]->MarkViewPortDirty(Pixmap->ViewPort());
delete Pixmap;
pixmaps[i] = NULL;
return;
}
}
esyslog("ERROR: attempt to destroy an unregistered pixmap");
}
}
cPixmap *cOsd::AddPixmap(cPixmap *Pixmap)
{
if (Pixmap) {
LOCK_PIXMAPS;
for (int i = 0; i < pixmaps.Size(); i++) {
if (!pixmaps[i])
return pixmaps[i] = Pixmap;
}
pixmaps.Append(Pixmap);
}
return Pixmap;
}
cPixmapMemory *cOsd::RenderPixmaps(void)
{
cPixmapMemory *Pixmap = NULL;
if (isTrueColor) {
LOCK_PIXMAPS;
// Collect overlapping dirty rectangles:
cRect d;
for (int i = 0; i < pixmaps.Size(); i++) {
if (cPixmap *pm = pixmaps[i]) {
if (!pm->DirtyViewPort().IsEmpty()) {
if (d.IsEmpty() || d.Intersects(pm->DirtyViewPort())) {
d.Combine(pm->DirtyViewPort());
pm->SetClean();
}
}
}
}
if (!d.IsEmpty()) {
//#define DebugDirty
#ifdef DebugDirty
static cRect OldDirty;
cRect NewDirty = d;
d.Combine(OldDirty);
OldDirty = NewDirty;
#endif
Pixmap = new cPixmapMemory(0, d);
Pixmap->Clear();
// Render the individual pixmaps into the resulting pixmap:
for (int Layer = 0; Layer < MAXPIXMAPLAYERS; Layer++) {
for (int i = 0; i < pixmaps.Size(); i++) {
if (cPixmap *pm = pixmaps[i]) {
if (pm->Layer() == Layer)
Pixmap->DrawPixmap(pm, d);
}
}
}
#ifdef DebugDirty
cPixmapMemory DirtyIndicator(7, NewDirty);
static tColor DirtyIndicatorColors[] = { 0x7FFFFF00, 0x7F00FFFF };
static int DirtyIndicatorIndex = 0;
DirtyIndicator.Fill(DirtyIndicatorColors[DirtyIndicatorIndex]);
DirtyIndicatorIndex = 1 - DirtyIndicatorIndex;
Pixmap->Render(&DirtyIndicator, DirtyIndicator.DrawPort(), DirtyIndicator.ViewPort().Point().Shifted(-Pixmap->ViewPort().Point()));
#endif
}
}
return Pixmap;
}
eOsdError cOsd::CanHandleAreas(const tArea *Areas, int NumAreas)
{
if (NumAreas > MAXOSDAREAS)
return oeTooManyAreas;
eOsdError Result = oeOk;
for (int i = 0; i < NumAreas; i++) {
if (Areas[i].x1 > Areas[i].x2 || Areas[i].y1 > Areas[i].y2 || Areas[i].x1 < 0 || Areas[i].y1 < 0)
return oeWrongAlignment;
for (int j = i + 1; j < NumAreas; j++) {
if (Areas[i].Intersects(Areas[j])) {
Result = oeAreasOverlap;
break;
}
}
if (Areas[i].bpp == 32) {
if (NumAreas > 1)
return oeTooManyAreas;
}
}
return Result;
}
eOsdError cOsd::SetAreas(const tArea *Areas, int NumAreas)
{
eOsdError Result = CanHandleAreas(Areas, NumAreas);
if (Result == oeOk) {
while (numBitmaps)
delete bitmaps[--numBitmaps];
for (int i = 0; i < pixmaps.Size(); i++) {
delete pixmaps[i];
pixmaps[i] = NULL;
}
width = height = 0;
isTrueColor = NumAreas == 1 && Areas[0].bpp == 32;
if (isTrueColor) {
width = Areas[0].x2 - Areas[0].x1 + 1;
height = Areas[0].y2 - Areas[0].y1 + 1;
cPixmap *Pixmap = CreatePixmap(0, cRect(Areas[0].x1, Areas[0].y1, width, height));
Pixmap->Clear();
bitmaps[numBitmaps++] = new cBitmap(10, 10, 8); // dummy bitmap for GetBitmap()
}
else {
for (int i = 0; i < NumAreas; i++) {
bitmaps[numBitmaps++] = new cBitmap(Areas[i].Width(), Areas[i].Height(), Areas[i].bpp, Areas[i].x1, Areas[i].y1);
width = max(width, Areas[i].x2 + 1);
height = max(height, Areas[i].y2 + 1);
}
}
}
else
esyslog("ERROR: cOsd::SetAreas returned %d (%s)", Result, Result < oeUnknown ? OsdErrorTexts[Result] : OsdErrorTexts[oeUnknown]);
return Result;
}
void cOsd::SaveRegion(int x1, int y1, int x2, int y2)
{
if (isTrueColor) {
delete savedPixmap;
cRect r(x1, y1, x2 - x1 + 1, y2 - y1 + 1);
savedPixmap = new cPixmapMemory(0, r);
savedPixmap->Copy(pixmaps[0], r, cPoint(0, 0));
}
else {
delete savedBitmap;
savedBitmap = new cBitmap(x2 - x1 + 1, y2 - y1 + 1, 8, x1, y1);
for (int i = 0; i < numBitmaps; i++)
savedBitmap->DrawBitmap(bitmaps[i]->X0(), bitmaps[i]->Y0(), *bitmaps[i]);
}
}
void cOsd::RestoreRegion(void)
{
if (isTrueColor) {
if (savedPixmap) {
pixmaps[0]->Copy(savedPixmap, savedPixmap->DrawPort(), savedPixmap->ViewPort().Point());
delete savedPixmap;
savedPixmap = NULL;
}
}
else {
if (savedBitmap) {
DrawBitmap(savedBitmap->X0(), savedBitmap->Y0(), *savedBitmap);
delete savedBitmap;
savedBitmap = NULL;
}
}
}
eOsdError cOsd::SetPalette(const cPalette &Palette, int Area)
{
if (isTrueColor)
return oeOk;
if (Area < numBitmaps) {
bitmaps[Area]->Take(Palette);
return oeOk;
}
return oeUnknown;
}
void cOsd::DrawImage(const cPoint &Point, const cImage &Image)
{
if (isTrueColor)
pixmaps[0]->DrawImage(Point, Image);
}
void cOsd::DrawImage(const cPoint &Point, int ImageHandle)
{
if (isTrueColor)
pixmaps[0]->DrawImage(Point, ImageHandle);
}
void cOsd::DrawPixel(int x, int y, tColor Color)
{
if (isTrueColor)
pixmaps[0]->DrawPixel(cPoint(x, y), Color);
else {
for (int i = 0; i < numBitmaps; i++)
bitmaps[i]->DrawPixel(x, y, Color);
}
}
void cOsd::DrawBitmap(int x, int y, const cBitmap &Bitmap, tColor ColorFg, tColor ColorBg, bool ReplacePalette, bool Overlay)
{
if (isTrueColor)
pixmaps[0]->DrawBitmap(cPoint(x, y), Bitmap, ColorFg, ColorBg, Overlay);
else {
for (int i = 0; i < numBitmaps; i++)
bitmaps[i]->DrawBitmap(x, y, Bitmap, ColorFg, ColorBg, ReplacePalette, Overlay);
}
}
void cOsd::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();
}
}
}