diff --git a/CONTRIBUTORS b/CONTRIBUTORS index fd06cd2a..ff821cc9 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -1260,6 +1260,8 @@ Reinhard Nissl for implementing cDeviceHook for implementing cDevice::GetCurrentlyTunedTransponder() for fixing DDS detection for HD resolution subtitles + for some valuable input during development of the TrueColor OSD, help with + debugging, and an implementation of the AlphaBlend() function. Richard Robson for reporting freezing replay if a timer starts while in Transfer Mode from the diff --git a/HISTORY b/HISTORY index ff9f7d49..7cbe0fa7 100644 --- a/HISTORY +++ b/HISTORY @@ -6481,7 +6481,7 @@ Video Disk Recorder Revision History from Osama Alrawab). See INSTALL for information on how to turn this on. - Added Arabian language texts (thanks to Osama Alrawab). -2010-12-29: Version 1.7.17 +2011-02-20: Version 1.7.17 - Updated the Estonian OSD texts (thanks to Arthur Konovalov). - Fixed following symbolic links in RemoveFileOrDir() (cont'd) (thanks to @@ -6519,3 +6519,17 @@ Video Disk Recorder Revision History recording is started that has a frame rate other than the default. - The include path to the freetype2 header files is now retrieved via a call to 'pkg-config --cflags freetype2' (suggested by Andreas Oberritter). +- The OSD now has full TrueColor support. There can be several "pixmaps" that can + be overlayed with alpha blending. All existing skins should work out of the box + with the TrueColor OSD - the only exception being cOsd::GetBitmap(). Since the + TrueColor OSD doesn't use bitmaps, this function will return a dummy bitmap, which + may not be what the plugin expects. As long as this bitmap is only used for setting + the palette, there is no problem. However, any other operations on this bitmap will + have no effect. See the description of the cPixmap functions in osd.h for details + about the new functionalities. + The "ST:TNG Panels" skin has been enhanced to automatically use the TrueColor OSD + if available. + The "osddemo" plugin has been extended to show some of the possibilities of the + TrueColor OSD if it is run on a system that actually provides TrueColor support. + Thanks to Reinhard Nissl for some valuable input, help with debugging, and an + implementation of the AlphaBlend() function. diff --git a/PLUGINS/src/osddemo/HISTORY b/PLUGINS/src/osddemo/HISTORY index 4217cb1e..1270fbf6 100644 --- a/PLUGINS/src/osddemo/HISTORY +++ b/PLUGINS/src/osddemo/HISTORY @@ -21,3 +21,7 @@ VDR Plugin 'osddemo' Revision History 2008-04-13: Version 0.1.3 - Fixed setting the OSD level (thanks to Wolfgang Rohdewald). + +2011-02-20: Version 0.2.0 + +- Added support for TrueColor OSD. diff --git a/PLUGINS/src/osddemo/README b/PLUGINS/src/osddemo/README index 50d36d8c..90ae2952 100644 --- a/PLUGINS/src/osddemo/README +++ b/PLUGINS/src/osddemo/README @@ -19,4 +19,11 @@ Demonstration of how a plugin can have its very own OSD setup. It's a very primitive game that opens a small window in which the user can draw lines with the Up, Down, Left and Right buttons. The color buttons are used to switch color. + +On a VDR system with TrueColor support it displays some of the +possibilities available with the TrueColor OSD. Once the "Animation" +pixmap is displayed, it can be moved around with the Up, Down, Left +and Right buttons. The Red button turns off the "Tiled Pixmaps" +display, and the Green button toggles the color display. + Press Ok to close the window. diff --git a/PLUGINS/src/osddemo/osddemo.c b/PLUGINS/src/osddemo/osddemo.c index 2a9e09a6..87d94638 100644 --- a/PLUGINS/src/osddemo/osddemo.c +++ b/PLUGINS/src/osddemo/osddemo.c @@ -3,12 +3,13 @@ * * See the README file for copyright information and how to reach the author. * - * $Id: osddemo.c 2.1 2008/04/13 12:59:57 kls Exp $ + * $Id: osddemo.c 2.2 2011/02/20 15:06:18 kls Exp $ */ +#include #include -static const char *VERSION = "0.1.3"; +static const char *VERSION = "0.2.0"; static const char *DESCRIPTION = "Demo of arbitrary OSD setup"; static const char *MAINMENUENTRY = "Osd Demo"; @@ -22,7 +23,7 @@ private: tColor color; public: cLineGame(void); - ~cLineGame(); + virtual ~cLineGame(); virtual void Show(void); virtual eOSState ProcessKey(eKeys Key); }; @@ -73,6 +74,364 @@ eOSState cLineGame::ProcessKey(eKeys Key) return state; } +// --- cTrueColorDemo -------------------------------------------------------- + +class cTrueColorDemo : public cOsdObject, public cThread { +private: + cOsd *osd; + cPoint cursor; + cRect cursorLimits; + bool clockwise; + cPixmap *destroyablePixmap; + cPixmap *toggleablePixmap; + virtual void Action(void); + cPixmap *CreateTextPixmap(const char *s, int Line, int Layer, tColor ColorFg, tColor ColorBg, const cFont *Font = NULL); +public: + cTrueColorDemo(void); + virtual ~cTrueColorDemo(); + virtual void Show(void); + virtual eOSState ProcessKey(eKeys Key); + }; + +cTrueColorDemo::cTrueColorDemo(void) +{ + osd = NULL; + clockwise = true; + destroyablePixmap = NULL; + toggleablePixmap = NULL; +} + +cTrueColorDemo::~cTrueColorDemo() +{ + delete osd; +} + +cPixmap *cTrueColorDemo::CreateTextPixmap(const char *s, int Line, int Layer, tColor ColorFg, tColor ColorBg, const cFont *Font) +{ + if (!Font) + Font = cFont::GetFont(fontOsd); + const int h = Font->Height(s); + int w = Font->Width(s); + cPixmap *Pixmap = osd->CreatePixmap(Layer, cRect((osd->Width() - w) / 2, Line, w, h)); + if (Pixmap) { + Pixmap->Clear(); + Pixmap->SetAlpha(0); + Pixmap->DrawText(cPoint(0, 0), s, ColorFg, ColorBg, Font); + } + return Pixmap; +} + +void cTrueColorDemo::Action(void) +{ + cPixmap *FadeInPixmap = NULL; + cPixmap *FadeOutPixmap = NULL; + cPixmap *MovePixmap = NULL; + cPixmap *NextPixmap = NULL; + cPixmap *TilePixmap = NULL; + cPixmap *ScrollPixmap = NULL; + cPixmap *AnimPixmap = NULL; + int FrameTime = 40; // ms + int FadeTime = 1000; // ms + int MoveTime = 4000; // ms + int TileTime = 6000; // ms + int ScrollWaitTime = 1000; // ms + int ScrollLineTime = 200; // ms + int ScrollTotalTime = 8000; // ms + uint64_t Start = 0; + uint64_t ScrollStartTime = 0; + int ScrollLineNumber = 0; + cPoint MoveStart, MoveEnd; + cPoint TileStart, TileEnd; + cPoint ScrollStart, ScrollEnd; + int Line = osd->Height() / 20; + int StartLine = Line; + cPoint OldCursor; + int State = 0; + while (Running()) { + cPixmap::Lock(); + bool Animated = false; + uint64_t Now = cTimeMs::Now(); + if (FadeInPixmap) { + double t = min(double(Now - Start) / FadeTime, 1.0); + int Alpha = t * ALPHA_OPAQUE; + FadeInPixmap->SetAlpha(Alpha); + if (t >= 1) + FadeInPixmap = NULL; + Animated = true; + } + if (FadeOutPixmap) { + double t = min(double(Now - Start) / FadeTime, 1.0); + int Alpha = ALPHA_OPAQUE - t * ALPHA_OPAQUE; + FadeOutPixmap->SetAlpha(Alpha); + if (t >= 1) + FadeOutPixmap = NULL; + Animated = true; + } + if (MovePixmap) { + double t = min(double(Now - Start) / MoveTime, 1.0); + int x = MoveStart.X() + t * (MoveEnd.X() - MoveStart.X()); + int y = MoveStart.Y() + t * (MoveEnd.Y() - MoveStart.Y()); + cRect r = MovePixmap->ViewPort(); + r.SetPoint(x, y); + MovePixmap->SetViewPort(r); + if (t >= 1) + MovePixmap = NULL; + Animated = true; + } + if (TilePixmap) { + double t = min(double(Now - Start) / TileTime, 1.0); + int x = TileStart.X() + t * (TileEnd.X() - TileStart.X()); + int y = TileStart.Y() + t * (TileEnd.Y() - TileStart.Y()); + TilePixmap->SetDrawPortPoint(cPoint(x, y)); + if (t >= 1) { + destroyablePixmap = TilePixmap; + TilePixmap = NULL; + } + Animated = true; + } + if (ScrollPixmap) { + if (int(Now - Start) > ScrollWaitTime) { + if (ScrollStartTime) { + double t = min(double(Now - ScrollStartTime) / ScrollLineTime, 1.0); + int x = ScrollStart.X() + t * (ScrollEnd.X() - ScrollStart.X()); + int y = ScrollStart.Y() + t * (ScrollEnd.Y() - ScrollStart.Y()); + ScrollPixmap->SetDrawPortPoint(cPoint(x, y)); + if (t >= 1) { + if (int(Now - Start) < ScrollTotalTime) { + cRect r = ScrollPixmap->DrawPort(); + r.SetPoint(-r.X(), -r.Y()); + ScrollPixmap->Pan(cPoint(0, 0), r); + const cFont *Font = cFont::GetFont(fontOsd); + cString s = cString::sprintf("Line %d", ++ScrollLineNumber); + ScrollPixmap->DrawRectangle(cRect(0, ScrollPixmap->ViewPort().Height(), ScrollPixmap->DrawPort().Width(), ScrollPixmap->DrawPort().Height()), clrTransparent); + ScrollPixmap->DrawText(cPoint(0, ScrollPixmap->ViewPort().Height()), s, clrYellow, clrTransparent, Font); + ScrollStartTime = Now; + } + else { + FadeOutPixmap = ScrollPixmap; + ScrollPixmap = NULL; + Start = cTimeMs::Now(); + } + } + } + else + ScrollStartTime = Now; + } + Animated = true; + } + if (AnimPixmap) { + int d = AnimPixmap->ViewPort().Height(); + if (clockwise) + d = -d; + cPoint p = AnimPixmap->DrawPort().Point().Shifted(0, d); + if (clockwise && p.Y() <= -AnimPixmap->DrawPort().Height()) + p.SetY(0); + else if (!clockwise && p.Y() > 0) + p.SetY(-(AnimPixmap->DrawPort().Height() - AnimPixmap->ViewPort().Height())); + AnimPixmap->SetDrawPortPoint(p); + } + if (!Animated) { + switch (State) { + case 0: { + if (cFont *Font = cFont::CreateFont(DefaultFontOsd, osd->Height() / 10)) { + FadeInPixmap = CreateTextPixmap("VDR", Line, 1, clrYellow, clrTransparent, Font); + if (FadeInPixmap) + Line += FadeInPixmap->DrawPort().Height(); + delete Font; + Start = cTimeMs::Now(); + } + State++; + } + break; + case 1: { + FadeInPixmap = CreateTextPixmap("Video Disk Recorder", Line, 3, clrYellow, clrTransparent); + if (FadeInPixmap) + Line += FadeInPixmap->DrawPort().Height(); + Start = cTimeMs::Now(); + State++; + } + break; + case 2: { + FadeInPixmap = CreateTextPixmap("True Color OSD Demo", Line, 1, clrYellow, clrTransparent); + if (FadeInPixmap) + Line += FadeInPixmap->DrawPort().Height(); + Start = cTimeMs::Now(); + State++; + } + break; + case 3: { + if (cFont *Font = cFont::CreateFont(DefaultFontOsd, osd->Height() / 10)) { + NextPixmap = CreateTextPixmap("Millions of colors", Line, 1, clrYellow, clrTransparent, Font); + if (NextPixmap) { + FadeInPixmap = NextPixmap; + } + Start = cTimeMs::Now(); + StartLine = Line; + Line += NextPixmap->DrawPort().Height(); + } + State++; + } + break; + case 4: { + Line += osd->Height() / 10; + int w = osd->Width() / 2; + int h = osd->Height() - Line - osd->Height() / 10; + cImage Image(cSize(w, h)); + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) + Image.SetPixel(cPoint(x, y), HsvToColor(360 * double(x) / w, 1 - double(y) / h, 1) | 0xDF000000); + } + if (cPixmap *Pixmap = osd->CreatePixmap(2, cRect((osd->Width() - w) / 2, Line, w, h))) { + Pixmap->DrawImage(cPoint(0, 0), Image); + toggleablePixmap = Pixmap; + } + State++; + } + break; + case 5: { + if (NextPixmap) { + MovePixmap = NextPixmap; + MoveStart = MovePixmap->ViewPort().Point(); + MoveEnd.Set(osd->Width() - MovePixmap->ViewPort().Width(), osd->Height() - MovePixmap->ViewPort().Height()); + Start = cTimeMs::Now(); + } + State++; + } + break; + case 6: { + TilePixmap = CreateTextPixmap("Tiled Pixmaps", StartLine, 1, clrRed, clrWhite); + if (TilePixmap) { + TilePixmap->SetViewPort(TilePixmap->ViewPort().Grown(TilePixmap->DrawPort().Width(), TilePixmap->DrawPort().Height())); + TilePixmap->SetAlpha(200); + TilePixmap->SetTile(true); + TileStart = TilePixmap->DrawPort().Point(); + TileEnd = TileStart.Shifted(TilePixmap->ViewPort().Width(), TilePixmap->ViewPort().Height()); + MovePixmap = TilePixmap; + MoveStart = MovePixmap->ViewPort().Point(); + MoveEnd.Set(10, osd->Height() - MovePixmap->ViewPort().Height() - 10); + Start = cTimeMs::Now(); + } + State++; + } + break; + case 7: { + const cFont *Font = cFont::GetFont(fontOsd); + const char *Text = "Scrolling Pixmaps"; + int w = Font->Width(Text); + int h = Font->Height(); + if (cPixmap *Pixmap = osd->CreatePixmap(2, cRect((osd->Width() - w) / 2, StartLine, w, 2 * h), cRect(0, 0, w, 3 * h))) { + Pixmap->Clear(); + Pixmap->DrawText(cPoint(0, 0), Text, clrYellow, clrTransparent, Font); + cString s = cString::sprintf("Line %d", ++ScrollLineNumber); + Pixmap->DrawText(cPoint(0, Pixmap->ViewPort().Height()), s, clrYellow, clrTransparent, Font); + ScrollPixmap = Pixmap; + ScrollStart.Set(0, 0); + ScrollEnd.Set(0, -h); + Start = cTimeMs::Now(); + } + State++; + } + break; + case 8: { + const cFont *Font = cFont::GetFont(fontSml); + const char *Text = "Animation"; + const int Size = Font->Width(Text) + 10; + const int NumDots = 12; + const int AnimFrames = NumDots; + AnimPixmap = osd->CreatePixmap(3, cRect((osd->Width() - Size) / 2, StartLine, Size, Size), cRect(0, 0, Size, Size * AnimFrames)); + if (AnimPixmap) { + AnimPixmap->SetAlpha(0); + AnimPixmap->Clear(); + const int Diameter = Size / 5; + int xc = Size / 2 - Diameter / 2; + for (int Frame = 0; Frame < AnimFrames; Frame++) { + AnimPixmap->DrawEllipse(cRect(0, Frame * Size, Size, Size), 0xDDFFFFFF); + int yc = Frame * Size + Size / 2 - Diameter / 2; + int Color = 0xFF; + int Delta = Color / NumDots / 3; + for (int a = 0; a < NumDots; a++) { + double t = 2 * M_PI * (Frame + a) / NumDots; + int x = xc + ((Size - Diameter) / 2 - 5) * cos(t); + int y = yc + ((Size - Diameter) / 2 - 5) * sin(t); + AnimPixmap->DrawEllipse(cRect(x, y, Diameter, Diameter), ArgbToColor(0xFF, Color, Color, Color)); + Color -= Delta; + } + AnimPixmap->DrawText(cPoint(0, Frame * Size), "Animation", clrBlack, clrTransparent, cFont::GetFont(fontSml), Size, Size, taCenter); + } + FadeInPixmap = AnimPixmap; + LOCK_THREAD; + OldCursor = cursor = AnimPixmap->ViewPort().Point(); + cursorLimits.Set(0, 0, osd->Width(), osd->Height()); + cursorLimits.SetRight(cursorLimits.Right() - Size); + cursorLimits.SetBottom(cursorLimits.Bottom() - Size); + cursorLimits.Grow(-10, -10); + Start = cTimeMs::Now(); + } + State++; + } + break; + case 9: { + LOCK_THREAD; + if (cursor != OldCursor) { + MovePixmap = AnimPixmap; + MoveStart = MovePixmap->ViewPort().Point(); + MoveEnd = OldCursor = cursor; + MoveTime = 500; + Start = cTimeMs::Now(); + } + } + break; + } + } + osd->Flush(); + cPixmap::Unlock(); + int Delta = cTimeMs::Now() - Now; + if (Delta < FrameTime) + cCondWait::SleepMs(FrameTime - Delta); + } +} + +void cTrueColorDemo::Show(void) +{ + osd = cOsdProvider::NewOsd(cOsd::OsdLeft(), cOsd::OsdTop(), 50); + if (osd) { + tArea Area = { 0, 0, cOsd::OsdWidth() - 1, cOsd::OsdHeight() - 1, 32 }; + if (osd->SetAreas(&Area, 1) == oeOk) { + osd->DrawRectangle(0, 0, osd->Width() -1 , osd->Height() - 1, clrGray50); + osd->Flush(); + Start(); + } + } +} + +eOSState cTrueColorDemo::ProcessKey(eKeys Key) +{ + eOSState state = cOsdObject::ProcessKey(Key); + if (state == osUnknown) { + LOCK_PIXMAPS; + LOCK_THREAD; + const int d = 80; + switch (Key & ~k_Repeat) { + case kUp: cursor.SetY(max(cursorLimits.Top(), cursor.Y() - d)); clockwise = false; break; + case kDown: cursor.SetY(min(cursorLimits.Bottom(), cursor.Y() + d)); clockwise = true; break; + case kLeft: cursor.SetX(max(cursorLimits.Left(), cursor.X() - d)); clockwise = false; break; + case kRight: cursor.SetX(min(cursorLimits.Right(), cursor.X() + d)); clockwise = true; break; + case kRed: if (destroyablePixmap) { + osd->DestroyPixmap(destroyablePixmap); + destroyablePixmap = NULL; + } + break; + case kGreen: if (toggleablePixmap) + toggleablePixmap->SetLayer(-toggleablePixmap->Layer()); + break; + case kOk: return osEnd; + default: return state; + } + state = osContinue; + } + return state; +} + // --- cPluginOsddemo -------------------------------------------------------- class cPluginOsddemo : public cPlugin { @@ -131,6 +490,8 @@ void cPluginOsddemo::Housekeeping(void) cOsdObject *cPluginOsddemo::MainMenuAction(void) { // Perform the action when selected from the main VDR menu. + if (cOsdProvider::SupportsTrueColor()) + return new cTrueColorDemo; return new cLineGame; } diff --git a/PLUGINS/src/skincurses/skincurses.c b/PLUGINS/src/skincurses/skincurses.c index 4abb8634..84076cd4 100644 --- a/PLUGINS/src/skincurses/skincurses.c +++ b/PLUGINS/src/skincurses/skincurses.c @@ -3,7 +3,7 @@ * * See the README file for copyright information and how to reach the author. * - * $Id: skincurses.c 2.4 2010/02/28 12:50:13 kls Exp $ + * $Id: skincurses.c 2.5 2011/01/04 08:52:03 kls Exp $ */ #include @@ -23,6 +23,7 @@ public: virtual int Width(const char *s) const { return s ? Utf8StrLen(s) : 0; } virtual int Height(void) const { return 1; } virtual void DrawText(cBitmap *Bitmap, int x, int y, const char *s, tColor ColorFg, tColor ColorBg, int Width) const {} + virtual void DrawText(cPixmap *Pixmap, int x, int y, const char *s, tColor ColorFg, tColor ColorBg, int Width) const {} }; static const cCursesFont Font; diff --git a/font.c b/font.c index 4ff4017d..227dd63f 100644 --- a/font.c +++ b/font.c @@ -6,7 +6,7 @@ * * BiDi support by Osama Alrawab @2008 Tripoli-Libya. * - * $Id: font.c 2.5 2010/09/19 11:49:19 kls Exp $ + * $Id: font.c 2.6 2011/02/20 14:15:38 kls Exp $ */ #include "font.h" @@ -118,6 +118,7 @@ public: virtual int Width(const char *s) const; virtual int Height(void) const { return height; } virtual void DrawText(cBitmap *Bitmap, int x, int y, const char *s, tColor ColorFg, tColor ColorBg, int Width) const; + virtual void DrawText(cPixmap *Pixmap, int x, int y, const char *s, tColor ColorFg, tColor ColorBg, int Width) const; }; cFreetypeFont::cFreetypeFont(const char *Name, int CharHeight, int CharWidth) @@ -329,6 +330,62 @@ void cFreetypeFont::DrawText(cBitmap *Bitmap, int x, int y, const char *s, tColo } } +void cFreetypeFont::DrawText(cPixmap *Pixmap, int x, int y, const char *s, tColor ColorFg, tColor ColorBg, int Width) const +{ + if (s && height) { // checking height to make sure we actually have a valid font +#ifdef BIDI + cString bs = Bidi(s); + s = bs; +#endif + bool AntiAliased = Setup.AntiAlias; + bool TransparentBackground = ColorBg == clrTransparent; + uint prevSym = 0; + while (*s) { + int sl = Utf8CharLen(s); + uint sym = Utf8CharGet(s, sl); + s += sl; + cGlyph *g = Glyph(sym, AntiAliased); + if (!g) + continue; + int kerning = Kerning(g, prevSym); + prevSym = sym; + uchar *buffer = g->Bitmap(); + int symWidth = g->Width(); + if (Width && x + symWidth + g->Left() + kerning - 1 > Width) + break; // we don't draw partial characters + if (x + symWidth + g->Left() + kerning > 0) { + for (int row = 0; row < g->Rows(); row++) { + for (int pitch = 0; pitch < g->Pitch(); pitch++) { + uchar bt = *(buffer + (row * g->Pitch() + pitch)); + if (AntiAliased) { + if (bt > 0x00) { + tColor bg; + if (bt == 0xFF || TransparentBackground) + bg = ColorFg; + else { + bg = AlphaBlend(ColorFg, ColorBg, bt); + bt = ALPHA_OPAQUE; + } + Pixmap->DrawPixel(cPoint(x + pitch + g->Left() + kerning, y + row + (height - Bottom() - g->Top())), bg, bt); + } + } + else { //monochrome rendering + for (int col = 0; col < 8 && col + pitch * 8 <= symWidth; col++) { + if (bt & 0x80) + Pixmap->DrawPixel(cPoint(x + col + pitch * 8 + g->Left() + kerning, y + row + (height - Bottom() - g->Top())), ColorFg); + bt <<= 1; + } + } + } + } + } + x += g->AdvanceX() + kerning; + if (x > Pixmap->DrawPort().Width() - 1) + break; + } + } +} + // --- cDummyFont ------------------------------------------------------------ // A dummy font, in case there are no fonts installed: @@ -339,6 +396,7 @@ public: virtual int Width(const char *s) const { return 50; } virtual int Height(void) const { return 20; } virtual void DrawText(cBitmap *Bitmap, int x, int y, const char *s, tColor ColorFg, tColor ColorBg, int Width) const {} + virtual void DrawText(cPixmap *Pixmap, int x, int y, const char *s, tColor ColorFg, tColor ColorBg, int Width) const {}; }; // --- cFont ----------------------------------------------------------------- diff --git a/font.h b/font.h index cb76671a..dbca88ef 100644 --- a/font.h +++ b/font.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: font.h 2.4 2010/09/19 11:48:37 kls Exp $ + * $Id: font.h 2.5 2011/01/14 16:22:03 kls Exp $ */ #ifndef __FONT_H @@ -26,6 +26,7 @@ enum eDvbFont { }; class cBitmap; +class cPixmap; typedef uint32_t tColor; // see also osd.h typedef uint8_t tIndex; @@ -54,6 +55,9 @@ public: virtual void DrawText(cBitmap *Bitmap, int x, int y, const char *s, tColor ColorFg, tColor ColorBg, int Width) const = 0; ///< Draws the given text into the Bitmap at position (x, y) with the given colors. ///< The text will not exceed the given Width (if > 0), and will end with a complete character. + virtual void DrawText(cPixmap *Pixmap, int x, int y, const char *s, tColor ColorFg, tColor ColorBg, int Width) const {}; // not "pure", so that existing implementations still compile + ///< Draws the given text into the Pixmap at position (x, y) with the given colors. + ///< The text will not exceed the given Width (if > 0), and will end with a complete character. static void SetFont(eDvbFont Font, const char *Name, int CharHeight); ///< Sets the given Font to use the font data according to Name (see CreateFont()) ///< and make its characters CharHeight pixels high. @@ -81,7 +85,7 @@ public: ///< of the actual font file. ///< Returns true if any font names were found. static cString GetFontFileName(const char *FontName); - ///< Retruns the actual font file name for the given FontName. + ///< Returns the actual font file name for the given FontName. #ifdef BIDI static cString Bidi(const char *Ltr); ///< Converts any "right-to-left" parts in the "left-to-right" string Ltr diff --git a/osd.c b/osd.c index 7a2b76ae..f2b5e8ab 100644 --- a/osd.c +++ b/osd.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: osd.c 2.11 2010/12/12 23:16:19 kls Exp $ + * $Id: osd.c 2.12 2011/02/20 14:48:01 kls Exp $ */ #include "osd.h" @@ -16,6 +16,96 @@ #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); + } +} + +#define USE_ALPHA_LUT +#ifdef USE_ALPHA_LUT +// Alpha blending with lookup table (by Reinhard Nissl ) +// A little faster than the implementation below, but requires some 500KB +// of RAM for the lookup table. +static uint16_t AlphaLut[256][256][4]; + +class cInitAlphaLut { +public: + cInitAlphaLut(void) + { + for (int alphaA = 0; alphaA < 256; alphaA++) { + for (int alphaB = 0; alphaB < 256; alphaB++) { + int alphaO_255 = 255 * alphaA + alphaB * (255 - alphaA); + if (!alphaO_255) + alphaO_255++; + int factorA = 256 * 255 * alphaA / alphaO_255; + int factorB = 256 * alphaB * (255 - alphaA) / alphaO_255; + AlphaLut[alphaA][alphaB][0] = factorA; + AlphaLut[alphaA][alphaB][1] = factorB; + // index 2 is never used + AlphaLut[alphaA][alphaB][3] = alphaO_255 / 255; + } + } + } + }; + +cInitAlphaLut initAlphaLut; + +tColor AlphaBlend(tColor ColorFg, tColor ColorBg, tColor AlphaLayer) +{ + tColor Alpha = (ColorFg & 0xFF000000) >> 24; + if (AlphaLayer < ALPHA_OPAQUE) { + Alpha *= AlphaLayer; + Alpha = ((Alpha + ((Alpha >> 8) & 0x000000FF) + 0x00000080) >> 8) & 0x000000FF; + } + uint16_t *lut = &AlphaLut[Alpha][(ColorBg & 0xFF000000) >> 24][0]; + return (tColor)((lut[3] << 24) + | (((((ColorFg & 0x00FF0000) >> 16) * lut[0] + ((ColorBg & 0x00FF0000) >> 16) * lut[1]) << 8) & 0x00FF0000) + | (((((ColorFg & 0x0000FF00) >> 8) * lut[0] + ((ColorBg & 0x0000FF00) >> 8) * lut[1]) ) & 0x0000FF00) + | (((((ColorFg & 0x000000FF) ) * lut[0] + ((ColorBg & 0x000000FF) ) * lut[1]) >> 8) & 0x000000FF)); +} +#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, tColor 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, tColor 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) @@ -612,8 +702,6 @@ void cBitmap::DrawEllipse(int x1, int y1, int x2, int y2, tColor Color, int Quad void cBitmap::DrawSlope(int x1, int y1, int x2, int y2, tColor Color, int Type) { - // TODO This is just a quick and dirty implementation of a slope drawing - // machanism. If somebody can come up with a better solution, let's have it! if (!Intersects(x1, y1, x2, y2)) return; bool upper = Type & 0x01; @@ -723,6 +811,729 @@ void cBitmap::ShrinkBpp(int NewBpp) } } +// --- 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 != layer) { + if (Layer > 0 || layer > 0) + MarkViewPortDirty(viewPort); + layer = Layer; + } + Unlock(); +} + +void cPixmap::SetAlpha(int Alpha) +{ + Lock(); + Alpha = min(max(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, bool Opaque) +{ + 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 (Opaque) + 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 (Opaque) + 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, tColor Alpha) +{ + Lock(); + if (DrawPort().Size().Contains(Point)) { + int p = Point.Y() * DrawPort().Width() + Point.X(); + data[p] = AlphaBlend(Color, data[p], Alpha); + 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) + ; + else if ((Alignment & taRight) != 0) { + if (w < Width) + x += Width - w; + } + else { // taCentered + if (w < Width) + x += (Width - w) / 2; + } + } + if (Height) { + if ((Alignment & taTop) != 0) + ; + else if ((Alignment & taBottom) != 0) { + if (h < Height) + y += Height - h; + } + else { // taCentered + if (h < Height) + y += (Height - h) / 2; + } + } + } + Font->DrawText(this, x, y, s, ColorFg, ColorBg, limit); + MarkDrawPortDirty(r); + Unlock(); +} + +void cPixmapMemory::DrawRectangle(const cRect &Rect, tColor Color) +{ + Lock(); + cRect r = Rect.Intersected(DrawPort().Size()); + if (!r.IsEmpty()) { + int wd = DrawPort().Width(); + int w = r.Width() * sizeof(tColor); + tColor *ps = NULL; + tColor *pd = data + wd * r.Top() + r.Left(); + for (int y = r.Height(); y-- > 0; ) { + if (ps) + memcpy(pd, ps, w); // all other lines are copied fast from the first one + else { + // explicitly fill the first line: + tColor *cd = ps = pd; + for (int x = r.Width(); x-- > 0; ) { + *cd = Color; + cd++; + } + } + pd += wd; + } + MarkDrawPortDirty(r); + } + Unlock(); +} + +void cPixmapMemory::DrawEllipse(const cRect &Rect, tColor Color, int Quadrants) +{ +//TODO use anti-aliasing? +//TODO fix alignment + Lock(); + // Algorithm based on http://homepage.smc.edu/kennedy_john/BELIPSE.PDF + int x1 = Rect.Left(); + int y1 = Rect.Top(); + int x2 = Rect.Right(); + int y2 = Rect.Bottom(); + int rx = x2 - x1; + int ry = y2 - y1; + int cx = (x1 + x2) / 2; + int cy = (y1 + y2) / 2; + switch (abs(Quadrants)) { + case 0: rx /= 2; ry /= 2; break; + case 1: cx = x1; cy = y2; break; + case 2: cx = x2; cy = y2; break; + case 3: cx = x2; cy = y1; break; + case 4: cx = x1; cy = y1; break; + case 5: cx = x1; ry /= 2; break; + case 6: cy = y2; rx /= 2; break; + case 7: cx = x2; ry /= 2; break; + case 8: cy = y1; rx /= 2; break; + default: ; + } + int TwoASquare = 2 * rx * rx; + int TwoBSquare = 2 * ry * ry; + int x = rx; + int y = 0; + int XChange = ry * ry * (1 - 2 * rx); + int YChange = rx * rx; + int EllipseError = 0; + int StoppingX = TwoBSquare * rx; + int StoppingY = 0; + while (StoppingX >= StoppingY) { + switch (Quadrants) { + case 5: DrawRectangle(cRect(cx, cy + y, x + 1, 1), Color); // no break + case 1: DrawRectangle(cRect(cx, cy - y, x + 1, 1), Color); break; + case 7: DrawRectangle(cRect(cx - x, cy + y, x + 1, 1), Color); // no break + case 2: DrawRectangle(cRect(cx - x, cy - y, x + 1, 1), Color); break; + case 3: DrawRectangle(cRect(cx - x, cy + y, x + 1, 1), Color); break; + case 4: DrawRectangle(cRect(cx, cy + y, x + 1, 1), Color); break; + case 0: + case 6: DrawRectangle(cRect(cx - x, cy - y, 2 * x + 1, 1), Color); if (Quadrants == 6) break; + case 8: DrawRectangle(cRect(cx - x, cy + y, 2 * x + 1, 1), Color); break; + case -1: DrawRectangle(cRect(cx + x, cy - y, x2 - 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, x2 - 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, x2 - 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, x2 - x + 1, 1), Color); break; + } + 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(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(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[] = { @@ -745,8 +1556,11 @@ cVector cOsd::Osds; cOsd::cOsd(int Left, int Top, uint Level) { - savedRegion = NULL; + isTrueColor = false; + savedBitmap = NULL; numBitmaps = 0; + savedPixmap = NULL; + numPixmaps = 0; left = Left; top = Top; width = height = 0; @@ -765,7 +1579,10 @@ cOsd::~cOsd() { for (int i = 0; i < numBitmaps; i++) delete bitmaps[i]; - delete savedRegion; + delete savedBitmap; + delete savedPixmap; + for (int i = 0; i < numPixmaps; i++) + delete pixmaps[i]; for (int i = 0; i < Osds.Size(); i++) { if (Osds[i] == this) { Osds.Remove(i); @@ -786,15 +1603,110 @@ void cOsd::SetOsdPosition(int Left, int Top, int Width, int Height) 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) { + if (isTrueColor) + Area = 0; // returns the dummy bitmap return Area < numBitmaps ? bitmaps[Area] : NULL; } +cPixmap *cOsd::CreatePixmap(int Layer, const cRect &ViewPort, const cRect &DrawPort) +{ + if (isTrueColor) { + LOCK_PIXMAPS; + cPixmap *Pixmap = new cPixmapMemory(Layer, ViewPort, DrawPort); + if (AddPixmap(Pixmap)) + return Pixmap; + delete Pixmap; + } + return NULL; +} + +void cOsd::DestroyPixmap(cPixmap *Pixmap) +{ + if (isTrueColor) { + LOCK_PIXMAPS; + for (int i = 1; i < numPixmaps; i++) { // begin at 1 - don't let the background pixmap be destroyed! + if (pixmaps[i] == Pixmap) { + pixmaps[0]->MarkViewPortDirty(Pixmap->ViewPort()); + delete Pixmap; + while (i < numPixmaps - 1) { + pixmaps[i] = pixmaps[i + 1]; + i++; + } + numPixmaps--; + return; + } + } + esyslog("ERROR: attempt to destroy an unregistered pixmap"); + } +} + +cPixmap *cOsd::AddPixmap(cPixmap *Pixmap) +{ + LOCK_PIXMAPS; + if (numPixmaps < MAXOSDPIXMAPS) + return pixmaps[numPixmaps++] = Pixmap; + else + esyslog("ERROR: too many OSD pixmaps requested (maximum is %d)", MAXOSDPIXMAPS); + return NULL; +} + +cPixmapMemory *cOsd::RenderPixmaps(void) +{ + cPixmapMemory *Pixmap = NULL; + if (isTrueColor) { + LOCK_PIXMAPS; + for (;;) { + // Collect overlapping dirty rectangles: + cRect d; + for (int i = 0; i < numPixmaps; i++) { + cPixmap *pm = pixmaps[i]; + if (!pm->DirtyViewPort().IsEmpty()) { + if (d.IsEmpty() || d.Intersects(pm->DirtyViewPort())) { + d.Combine(pm->DirtyViewPort()); + pm->SetClean(); + } + } + } + if (d.IsEmpty()) + break; +//#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 < numPixmaps; i++) { + cPixmap *pm = pixmaps[i]; + if (pm->Layer() == Layer) + Pixmap->DrawPixmap(pm, d, i == 0); + } + } +#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) @@ -809,6 +1721,10 @@ eOsdError cOsd::CanHandleAreas(const tArea *Areas, int NumAreas) break; } } + if (Areas[i].bpp == 32) { + if (NumAreas > 1) + return oeTooManyAreas; + } } return Result; } @@ -820,11 +1736,21 @@ eOsdError cOsd::SetAreas(const tArea *Areas, int NumAreas) while (numBitmaps) delete bitmaps[--numBitmaps]; width = height = 0; - 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); - } + 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]); @@ -833,62 +1759,119 @@ eOsdError cOsd::SetAreas(const tArea *Areas, int NumAreas) void cOsd::SaveRegion(int x1, int y1, int x2, int y2) { - delete savedRegion; - savedRegion = new cBitmap(x2 - x1 + 1, y2 - y1 + 1, 8, x1, y1); - for (int i = 0; i < numBitmaps; i++) - savedRegion->DrawBitmap(bitmaps[i]->X0(), bitmaps[i]->Y0(), *bitmaps[i]); + 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 (savedRegion) { - DrawBitmap(savedRegion->X0(), savedRegion->Y0(), *savedRegion); - delete savedRegion; - savedRegion = NULL; + 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 (Area < numBitmaps) + 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) { - for (int i = 0; i < numBitmaps; i++) - bitmaps[i]->DrawPixel(x, y, 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) { - for (int i = 0; i < numBitmaps; i++) - bitmaps[i]->DrawBitmap(x, y, Bitmap, ColorFg, ColorBg, ReplacePalette, Overlay); + if (isTrueColor) + pixmaps[0]->DrawBitmap(cPoint(x, y), Bitmap, ColorFg, ColorBg, Overlay); + else { + for (int i = 0; i < numBitmaps; i++) + bitmaps[i]->DrawBitmap(x, y, Bitmap, ColorFg, ColorBg, ReplacePalette, Overlay); + } } void cOsd::DrawText(int x, int y, const char *s, tColor ColorFg, tColor ColorBg, const cFont *Font, int Width, int Height, int Alignment) { - for (int i = 0; i < numBitmaps; i++) - bitmaps[i]->DrawText(x, y, s, ColorFg, ColorBg, Font, Width, Height, 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) { - for (int i = 0; i < numBitmaps; i++) - bitmaps[i]->DrawRectangle(x1, y1, x2, y2, 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) { - for (int i = 0; i < numBitmaps; i++) - bitmaps[i]->DrawEllipse(x1, y1, x2, y2, Color, 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) { - for (int i = 0; i < numBitmaps; i++) - bitmaps[i]->DrawSlope(x1, y1, x2, y2, Color, 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) @@ -901,6 +1884,7 @@ cOsdProvider *cOsdProvider::osdProvider = NULL; int cOsdProvider::oldWidth = 0; int cOsdProvider::oldHeight = 0; double cOsdProvider::oldAspect = 1.0; +cImage *cOsdProvider::images[MAXOSDIMAGES] = { NULL }; cOsdProvider::cOsdProvider(void) { @@ -957,6 +1941,58 @@ void cOsdProvider::UpdateOsdSize(bool Force) } } +bool cOsdProvider::SupportsTrueColor(void) +{ + if (osdProvider) { + return osdProvider->ProvidesTrueColor(); + } + else + esyslog("ERROR: no OSD provider available in call to SupportsTrueColor()"); + return false; +} + +int cOsdProvider::StoreImageData(const cImage &Image) +{ + LOCK_PIXMAPS; + for (int i = 1; i < MAXOSDIMAGES; i++) { + if (!images[i]) { + images[i] = new cImage(Image); + return i; + } + } + return 0; +} + +void cOsdProvider::DropImageData(int ImageHandle) +{ + LOCK_PIXMAPS; + if (0 < ImageHandle && ImageHandle < MAXOSDIMAGES) { + delete images[ImageHandle]; + images[ImageHandle] = NULL; + } +} + +const cImage *cOsdProvider::GetImageData(int ImageHandle) +{ + LOCK_PIXMAPS; + if (0 < ImageHandle && ImageHandle < MAXOSDIMAGES) + return images[ImageHandle]; + return NULL; +} + +int cOsdProvider::StoreImage(const cImage &Image) +{ + if (osdProvider) + return osdProvider->StoreImageData(Image); + return -1; +} + +void cOsdProvider::DropImage(int ImageHandle) +{ + if (osdProvider) + osdProvider->DropImageData(ImageHandle); +} + void cOsdProvider::Shutdown(void) { delete osdProvider; diff --git a/osd.h b/osd.h index aa8a35ab..2bb6e0a0 100644 --- a/osd.h +++ b/osd.h @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: osd.h 2.5 2010/01/17 13:23:50 kls Exp $ + * $Id: osd.h 2.6 2011/02/20 14:52:17 kls Exp $ */ #ifndef __OSD_H @@ -15,12 +15,15 @@ #include #include "config.h" #include "font.h" +#include "thread.h" #include "tools.h" #define OSD_LEVEL_DEFAULT 0 #define OSD_LEVEL_SUBTITLES 10 #define MAXNUMCOLORS 256 +#define ALPHA_TRANSPARENT 0x00 +#define ALPHA_OPAQUE 0xFF enum { //AARRGGBB @@ -50,6 +53,28 @@ enum eOsdError { oeOk, // see also OsdErrorTexts in osd.c typedef uint32_t tColor; // see also font.h typedef uint8_t tIndex; +inline tColor ArgbToColor(uint8_t A, uint8_t R, uint8_t G, uint8_t B) +{ + return (tColor(A) << 24) | (tColor(R) << 16) | (tColor(G) << 8) | B; +} + +inline tColor RgbToColor(uint8_t R, uint8_t G, uint8_t B) +{ + return (tColor(R) << 16) | (tColor(G) << 8) | B; +} + +inline tColor RgbToColor(double R, double G, double B) +{ + return RgbToColor(uint8_t(0xFF * R), uint8_t(0xFF * G), uint8_t(0xFF * B)); +} + +tColor HsvToColor(double H, double S, double V); + ///< Converts the given Hue (0..360), Saturation (0..1) and Value (0..1) + ///< to an RGB tColor value. The alpha value of the result is 0x00, so + ///< the caller may need to set it accordingly. + +tColor AlphaBlend(tColor ColorFg, tColor ColorBg, tColor AlphaLayer = ALPHA_OPAQUE); + class cPalette { private: tColor color[MAXNUMCOLORS]; @@ -257,16 +282,424 @@ struct tArea { bool Intersects(const tArea &Area) const { return !(x2 < Area.x1 || x1 > Area.x2 || y2 < Area.y1 || y1 > Area.y2); } }; +class cPoint { +private: + int x; + int y; +public: + cPoint(void) { x = y = 0; } + cPoint(int X, int Y) { x = X; y = Y; } + cPoint(const cPoint &Point) { x = Point.X(); y = Point.Y(); } + bool operator==(const cPoint &Point) const { return x == Point.X() && y == Point.Y(); } + bool operator!=(const cPoint &Point) const { return !(*this == Point); } + cPoint operator-(void) const { return cPoint(-x, -y); } + cPoint operator-(const cPoint &Point) const { return cPoint(x - Point.X(), y - Point.Y()); } + int X(void) const { return x; } + int Y(void) const { return y; } + void SetX(int X) { x = X; } + void SetY(int Y) { y = Y; } + void Set(int X, int Y) { x = X; y = Y; } + void Set(const cPoint &Point) { x = Point.X(); y = Point.Y(); } + void Shift(int Dx, int Dy) { x += Dx; y += Dy; } + void Shift(const cPoint &Dp) { x += Dp.X(); y += Dp.Y(); } + cPoint Shifted(int Dx, int Dy) const { cPoint p(*this); p.Shift(Dx, Dy); return p; } + cPoint Shifted(const cPoint &Dp) const { cPoint p(*this); p.Shift(Dp); return p; } + }; + +class cSize { +private: + int width; + int height; +public: + cSize(void) { width = height = 0; } + cSize(int Width, int Height) { width = Width; height = Height; } + cSize(const cSize &Size) { width = Size.Width(); height = Size.Height(); } + bool operator==(const cSize &Size) const { return width == Size.Width() && height == Size.Height(); } + bool operator!=(const cSize &Size) const { return !(*this == Size); } + bool operator<(const cSize &Size) const { return width < Size.Width() && height < Size.Height(); } + int Width(void) const { return width; } + int Height(void) const { return height; } + void SetWidth(int Width) { width = Width; } + void SetHeight(int Height) { height = Height; } + void Set(int Width, int Height) { width = Width; height = Height; } + void Set(const cSize &Size) { width = Size.Width(); height = Size.Height(); } + bool Contains(const cPoint &Point) const { return 0 <= Point.X() && 0 <= Point.Y() && Point.X() < width && Point.Y() < height; } + void Grow(int Dw, int Dh) { width += 2 * Dw; height += 2 * Dh; } + cSize Grown(int Dw, int Dh) const { cSize s(*this); s.Grow(Dw, Dh); return s; } + }; + +class cRect { +private: + cPoint point; + cSize size; +public: + static const cRect Null; + cRect(void): point(0, 0), size(0, 0) {} + cRect(int X, int Y, int Width, int Height): point(X, Y), size(Width, Height) {} + cRect(const cPoint &Point, const cSize &Size): point(Point), size(Size) {} + cRect(const cSize &Size): point(0, 0), size(Size) {} + cRect(const cRect &Rect): point(Rect.Point()), size(Rect.Size()) {} + bool operator==(const cRect &Rect) const { return point == Rect.Point() && size == Rect.Size(); } + bool operator!=(const cRect &Rect) const { return !(*this == Rect); } + int X(void) const { return point.X(); } + int Y(void) const { return point.Y(); } + int Width(void) const { return size.Width(); } + int Height(void) const { return size.Height(); } + int Left(void) const { return X(); } + int Top(void) const { return Y(); } + int Right(void) const { return X() + Width() - 1; } + int Bottom(void) const { return Y() + Height() - 1; } + const cPoint &Point(void) const { return point; } + const cSize &Size(void) const { return size; } + void Set(int X, int Y, int Width, int Height) { point.Set(X, Y); size.Set(Width, Height); } + void Set(cPoint Point, cSize Size) { point.Set(Point); size.Set(Size); } + void SetPoint(int X, int Y) { point.Set(X, Y); } + void SetPoint(const cPoint &Point) { point.Set(Point); } + void SetSize(int Width, int Height) { size.Set(Width, Height); } + void SetSize(const cSize &Size) { size.Set(Size); } + void SetX(int X) { point.SetX(X); } + void SetY(int Y) { point.SetY(Y); } + void SetWidth(int Width) { size.SetWidth(Width); } + void SetHeight(int Height) { size.SetHeight(Height); } + void SetLeft(int Left) { SetWidth(Width() + X() - Left); SetX(Left); } + void SetTop(int Top) { SetHeight(Height() + Y() - Top); SetY(Top); } + void SetRight(int Right) { SetWidth(Right - X() + 1); } + void SetBottom(int Bottom) { SetHeight(Bottom - Y() + 1); } + void Shift(int Dx, int Dy) { point.Shift(Dx, Dy); } + void Shift(const cPoint &Dp) { point.Shift(Dp); } + cRect Shifted(int Dx, int Dy) const { cRect r(*this); r.Shift(Dx, Dy); return r; } + cRect Shifted(const cPoint &Dp) const { cRect r(*this); r.Shift(Dp); return r; } + void Grow(int Dx, int Dy); + ///< Grows the rectangle by the given number of pixels in either direction. + ///< A negative value will shrink the rectangle. + cRect Grown(int Dw, int Dh) const { cRect r(*this); r.Grow(Dw, Dh); return r; } + bool Contains(const cPoint &Point) const; + ///< Returns true if this rectangle contains Point. + bool Contains(const cRect &Rect) const; + ///< Returns true if this rectangle completely contains Rect. + bool Intersects(const cRect &Rect) const; + ///< Returns true if this rectangle intersects with Rect. + cRect Intersected(const cRect &Rect) const; + ///< Returns the intersection of this rectangle and the given Rect. + void Combine(const cRect &Rect); + ///< Combines this rectangle with the given Rect. + cRect Combined(const cRect &Rect) const { cRect r(*this); r.Combine(Rect); return r; } + ///< Returns the surrounding rectangle that contains this rectangle and the + ///< given Rect. + void Combine(const cPoint &Point); + ///< Combines this rectangle with the given Point. + cRect Combined(const cPoint &Point) const { cRect r(*this); r.Combine(Point); return r; } + ///< Returns the surrounding rectangle that contains this rectangle and the + ///< given Point. + bool IsEmpty(void) const { return Width() <= 0 || Height() <= 0; } + ///< Returns true if this rectangle is empty. + }; + +class cImage { +private: + cSize size; + tColor *data; +public: + cImage(void); + cImage(const cImage &Image); + cImage(const cSize &Size, const tColor *Data = NULL); + ///< Creates an image with the given Size and allocates the necessary memory + ///< to copy the pixels pointed to by Data, which is a sequence of + ///< (Size.Width() * Size.Height()) tColor values. + ///< If Data is NULL, the allocated memory is not initialized. + ///< The alpha value of the Image's pixels is taken into account, so it has to be + ///< greater than 0 for the image to be visible. + virtual ~cImage(); + const cSize &Size(void) const { return size; } + int Width(void) const { return size.Width(); } + int Height(void) const { return size.Height(); } + const tColor *Data(void) const { return data; } + tColor GetPixel(const cPoint &Point) const { return data[size.Width() * Point.Y() + Point.X()]; } + ///< Returns the pixel value at the given Point. + ///< For performance reasons there is no range check here, so the caller + ///< must make sure that the Point is within the images size. + void SetPixel(const cPoint &Point, tColor Color) { data[size.Width() * Point.Y() + Point.X()] = Color; } + ///< Sets the pixel at the given Point to Color. + ///< For performance reasons there is no range check here, so the caller + ///< must make sure that the Point is within the images size. + void Clear(void); + ///< Clears the image data by setting all pixels to be fully transparent. + void Fill(tColor Color); + ///< Fills the image data with the given Color. + }; + +#define MAXPIXMAPLAYERS 8 + +class cPixmap { + friend class cOsd; + friend class cPixmapMutexLock; +private: + static cMutex mutex; + int layer; + int alpha; + bool tile; + cRect viewPort; + cRect drawPort; + cRect dirtyViewPort; + cRect dirtyDrawPort; +protected: + virtual ~cPixmap() {} + void MarkViewPortDirty(const cRect &Rect); + ///< Marks the given rectangle of the view port of this pixmap as dirty. + ///< Rect is combined with the existing dirtyViewPort rectangle. + ///< The coordinates of Rect are given in absolute OSD values. + void MarkViewPortDirty(const cPoint &Point); + ///< Marks the given point of the view port of this pixmap as dirty. + ///< Point is combined with the existing dirtyViewPort rectangle. + ///< The coordinates of Point are given in absolute OSD values. + void MarkDrawPortDirty(const cRect &Rect); + ///< Marks the given rectangle of the draw port of this pixmap as dirty. + ///< Rect is combined with the existing dirtyDrawPort rectangle. + ///< The coordinates of Rect are relative to the pixmap's draw port. + ///< If Rect extends into the currently visible view port of this pixmap, + ///< MarkViewPortDirty() is called with the appropriate value. + void MarkDrawPortDirty(const cPoint &Point); + ///< Marks the given point of the draw port of this pixmap as dirty. + ///< Point is combined with the existing dirtyDrawPort rectangle. + ///< The coordinates of Point are relative to the pixmap's draw port. + ///< If Point is within the currently visible view port of this pixmap, + ///< MarkViewPortDirty() is called with the appropriate value. + virtual void DrawPixmap(const cPixmap *Pixmap, const cRect &Dirty, bool Opaque); + ///< Draws the Dirty part of the given Pixmap into this pixmap. If Opaque + ///< is true, the Pixmap is copied, otherwise it is rendered into this + ///< pixmap. This function is used only to implement the tile handling + ///< in the final rendering to the OSD. +public: + cPixmap(void); + cPixmap(int Layer, const cRect &ViewPort, const cRect &DrawPort = cRect::Null); + ///< Creates a pixmap in the given Layer. When rendering the final OSD, pixmaps + ///< are handled in ascending order of their individual layer. This is + ///< important if pixmaps overlap each other. The one with the highest layer is + ///< rendered last. The actual value of Layer doesn't matter, it is only used + ///< for defining the rendering sequence. If Layer is less than zero, this + ///< pixmap will not be rendered into the final OSD (it can be activated by a + ///< later call to SetLayer()). The value 0 is reserved for the background + ///< pixmap and shall not be used otherwise. If there are several pixmaps with + ///< the same value of Layer, their rendering sequence within that layer is + ///< undefined. + ///< I order to allow devices that can handle only a limited number of layers, + ///< the Layer parameters must be less than 8 (MAXPIXMAPLAYERS). + ///< ViewPort defines the rectangle in which this pixmap will be rendered on + ///< the OSD. If no DrawPort ist given, it defaults to the same size as the + ///< ViewPort, with its upper left corner set to (0, 0). + ///< All drawing operations will be executed relative to the origin of the + ///< DrawPort rectangle, and will be clipped to the size of this rectangle. + ///< The DrawPort may have a different size than the ViewPort. If it is smaller + ///< than the ViewPort, the rest of the ViewPort is treated as fully transparent + ///< (unless this is a tiled pixmap, in which case the DrawPort is repeated + ///< horizontally and vertically to fill the entire ViewPort). If the DrawPort + ///< is larger than the ViewPort, only that portion of the DrawPort that + ///< intersects with the ViewPort will be visible on the OSD. + ///< The drawing area of a newly created cPixmap is not initialized and may + ///< contain random data. + static void Lock(void) { mutex.Lock(); } + ///< All member functions of cPixmap set locks as necessary to make sure + ///< they are thread-safe. If several cPixmap member functions need to be + ///< called in a row, the caller must surround these calls with proper + ///< Lock()/Unlock() calls. See the LOCK_PIXMAPS macro for a convenient + ///< way of doing this. + static void Unlock(void) { mutex.Unlock(); } + int Layer(void) const { return layer; } + int Alpha(void) const { return alpha; } + bool Tile(void) const { return tile; } + const cRect &ViewPort(void) const { return viewPort; } + ///< Returns the pixmap's view port, which is relative to the OSD's origin. + const cRect &DrawPort(void) const { return drawPort; } + ///< Returns the pixmap's draw port, which is relative to the view port. + const cRect &DirtyViewPort(void) const { return dirtyViewPort; } + ///< Returns the "dirty" rectangle this pixmap causes on the OSD. This is the + ///< surrounding rectangle around all pixels that have been modified since the + ///< last time this pixmap has been rendered to the OSD. The rectangle is + ///< relative to the OSD's origin. + const cRect &DirtyDrawPort(void) const { return dirtyDrawPort; } + ///< Returns the "dirty" rectangle in the draw port of this this pixmap. This is + ///< the surrounding rectangle around all pixels that have been modified since the + ///< last time this pixmap has been rendered to the OSD. The rectangle is + ///< relative to the draw port's origin. + void SetClean(void); + ///< Resets the "dirty" rectangles of this pixmap. + virtual void SetLayer(int Layer); + ///< Sets the layer of this pixmap to the given value. + ///< If the new layer is greater than zero, the pixmap will be visible. + ///< If it is less than zero, it will be invisible. + ///< A value of 0 will be silently ignored. + ///< If a derived class reimplements this function, it needs to call the base + ///< class function. + virtual void SetAlpha(int Alpha); + ///< Sets the alpha value of this pixmap to the given value. + ///< Alpha is limited to the range 0 (fully transparent) to 255 (fully opaque). + ///< If a derived class reimplements this function, it needs to call the base + ///< class function. + virtual void SetTile(bool Tile); + ///< Sets the tile property of this pixmap to the given value. If Tile is true, + ///< the pixmaps data will be repeated horizontally and vertically if necessary + ///< to fill the entire view port. + ///< If a derived class reimplements this function, it needs to call the base + ///< class function. + virtual void SetViewPort(const cRect &Rect); + ///< Sets the pixmap's view port to the given Rect. + ///< If a derived class reimplements this function, it needs to call the base + ///< class function. + virtual void SetDrawPortPoint(const cPoint &Point, bool Dirty = true); + ///< Sets the pixmap's draw port to the given Point. + ///< Only the origin point of the draw port can be modified, its size is fixed. + ///< By default, setting a new draw port point results in marking the relevant + ///< part of the view port as "drity". If Dirty is set to false, the view port + ///< will not be marked as dirty. This is mainly used to implement the Pan() + ///< function. + ///< If a derived class reimplements this function, it needs to call the base + ///< class function. + virtual void Clear(void) = 0; + ///< Clears the pixmap's draw port by setting all pixels to be fully transparent. + ///< A derived class must call Lock()/Unlock(). + virtual void Fill(tColor Color) = 0; + ///< Fills the pixmap's draw port with the given Color. + ///< A derived class must call Lock()/Unlock(). + virtual void DrawImage(const cPoint &Point, const cImage &Image) = 0; + ///< Draws the given Image into this pixmap at the given Point. + virtual void DrawImage(const cPoint &Point, int ImageHandle) = 0; + ///< Draws the image referenced by the given ImageHandle into this pixmap at + ///< the given Point. ImageHandle must be a value that has previously been + ///< returned by a call to cOsdProvider::StoreImage(). If ImageHandle + ///< has an invalid value, nothing happens. + virtual void DrawPixel(const cPoint &Point, tColor Color, tColor Alpha = ALPHA_OPAQUE) = 0; + ///< Sets the pixel at the given Point to the given Color, which is + ///< a full 32 bit ARGB value. If the alpha value of Color is not 0xFF + ///< (fully opaque), the pixel is alpha blended with the existing color + ///< at the given position in this pixmap. If Alpha is less than + ///< ALPHA_OPAQUE, the alpha value of Color will be reduced accordingly. + virtual void DrawBitmap(const cPoint &Point, const cBitmap &Bitmap, tColor ColorFg = 0, tColor ColorBg = 0, bool Overlay = false) = 0; + ///< Sets the pixels in the OSD with the data from the given + ///< Bitmap, putting the upper left corner of the Bitmap at Point. + ///< If ColorFg or ColorBg is given, the first palette entry of the Bitmap + ///< will be mapped to ColorBg and the second palette entry will be mapped to + ///< ColorFg (palette indexes are defined so that 0 is the background and + ///< 1 is the foreground color). + ///< If Overlay is true, any pixel in Bitmap that has color index 0 will + ///< not overwrite the corresponding pixel in the target area. + ///< This function is mainly for compatibility with skins or plugins that + ///< draw bitmaps onto the OSD. + virtual void DrawText(const cPoint &Point, const char *s, tColor ColorFg, tColor ColorBg, const cFont *Font, int Width = 0, int Height = 0, int Alignment = taDefault) = 0; + ///< Draws the given string at Point with the given foreground + ///< and background color and font. If Width and Height are given, the text + ///< will be drawn into a rectangle with the given size and the given + ///< Alignment (default is top-left). If ColorBg is clrTransparent, no + ///< background pixels will be drawn, which allows drawing "transparent" text. + virtual void DrawRectangle(const cRect &Rect, tColor Color) = 0; + ///< Draws a filled rectangle with the given Color. + virtual void DrawEllipse(const cRect &Rect, tColor Color, int Quadrants = 0) = 0; + ///< Draws a filled ellipse with the given Color that fits into the given + ///< rectangle. Quadrants controls which parts of the ellipse are actually drawn: + ///< 0 draws the entire ellipse + ///< 1..4 draws only the first, second, third or fourth quadrant, respectively + ///< 5..8 draws the right, top, left or bottom half, respectively + ///< -1..-8 draws the inverted part of the given quadrant(s) + ///< If Quadrants is not 0, the coordinates are those of the actual area, not + ///< the full circle! + virtual void DrawSlope(const cRect &Rect, tColor Color, int Type) = 0; + ///< Draws a "slope" with the given Color into the given rectangle. + ///< Type controls the direction of the slope and which side of it will be drawn: + ///< 0: horizontal, rising, lower + ///< 1: horizontal, rising, upper + ///< 2: horizontal, falling, lower + ///< 3: horizontal, falling, upper + ///< 4: vertical, rising, lower + ///< 5: vertical, rising, upper + ///< 6: vertical, falling, lower + ///< 7: vertical, falling, upper + virtual void Render(const cPixmap *Pixmap, const cRect &Source, const cPoint &Dest) = 0; + ///< Renders the part of the given Pixmap covered by Source into this pixmap at + ///< location Dest. The Source rectangle is relative to the given Pixmap's draw port. + ///< The Pixmap's alpha value is to be used when rendering. + virtual void Copy(const cPixmap *Pixmap, const cRect &Source, const cPoint &Dest) = 0; + ///< Copies the part of the given Pixmap covered by Source into this pixmap at + ///< location Dest. The Source rectangle is relative to the given Pixmap's draw port. + ///< The data from Pixmap is copied as is, no alpha handling of any kind takes + ///< place. + virtual void Scroll(const cPoint &Dest, const cRect &Source = cRect::Null) = 0; + ///< Scrolls the data in the pixmap's draw port to the given Dest point. + ///< If Source is given, only the data within that rectangle is scrolled. + ///< Source and Dest are relative to this pixmap's draw port. + virtual void Pan(const cPoint &Dest, const cRect &Source = cRect::Null) = 0; + ///< Does the same as Scroll(), but also shifts the draw port accordingly, + ///< so that the view port doesn't get dirty if the scrolled rectangle + ///< covers the entire view port. This may be of advantage if, e.g., + ///< there is a draw port that holds, say, 11 lines of text, while the + ///< view port displays only 10 lines. By Pan()'ing the draw port up one + ///< line, an new bottom line can be written into the draw port (without + ///< being seen through the view port), and later the draw port can be + ///< shifted smoothly, resulting in a smooth scrolling. + ///< It is the caller's responsibility to make sure that Source and Dest + ///< are given in such a way that the view port will not get dirty. No + ///< check is done whether this condition actually holds true. + }; + +class cPixmapMutexLock : public cMutexLock { +public: + cPixmapMutexLock(void): cMutexLock(&cPixmap::mutex) {} + }; + +#define LOCK_PIXMAPS cPixmapMutexLock PixmapMutexLock + +// cPixmapMemory is an implementation of cPixmap that uses an array of tColor +// values to store the pixmap. + +class cPixmapMemory : public cPixmap { +private: + tColor *data; + bool panning; +public: + cPixmapMemory(void); + cPixmapMemory(int Layer, const cRect &ViewPort, const cRect &DrawPort = cRect::Null); + virtual ~cPixmapMemory(); + const uint8_t *Data(void) { return (uint8_t *)data; } + virtual void Clear(void); + virtual void Fill(tColor Color); + virtual void DrawImage(const cPoint &Point, const cImage &Image); + virtual void DrawImage(const cPoint &Point, int ImageHandle); + virtual void DrawPixel(const cPoint &Point, tColor Color, tColor Alpha = ALPHA_OPAQUE); + virtual void DrawBitmap(const cPoint &Point, const cBitmap &Bitmap, tColor ColorFg = 0, tColor ColorBg = 0, bool Overlay = false); + virtual void DrawText(const cPoint &Point, const char *s, tColor ColorFg, tColor ColorBg, const cFont *Font, int Width = 0, int Height = 0, int Alignment = taDefault); + virtual void DrawRectangle(const cRect &Rect, tColor Color); + virtual void DrawEllipse(const cRect &Rect, tColor Color, int Quadrants = 0); + virtual void DrawSlope(const cRect &Rect, tColor Color, int Type); + virtual void Render(const cPixmap *Pixmap, const cRect &Source, const cPoint &Dest); + virtual void Copy(const cPixmap *Pixmap, const cRect &Source, const cPoint &Dest); + virtual void Scroll(const cPoint &Dest, const cRect &Source = cRect::Null); + virtual void Pan(const cPoint &Dest, const cRect &Source = cRect::Null); + }; + #define MAXOSDAREAS 16 +#define MAXOSDPIXMAPS 64 + +/// The cOsd class is the interface to the "On Screen Display". +/// An actual output device needs to derive from this class and implement +/// the functionality necessary to display the OSD on the TV screen. +/// If the actual OSD supports "True Color", it can either let VDR do +/// all the rendering by calling RenderPixmaps() ("raw mode"), or it can +/// reimplement all necessary cPixmap functions and do the rendering +/// itself ("high level mode"). +/// If an OSD provides a "high level mode", it shall also provide a "raw mode" +/// in order to verify proper operation. The plugin that impements the OSD +/// shall offer a configuration switch in its setup. class cOsd { friend class cOsdProvider; private: static int osdLeft, osdTop, osdWidth, osdHeight; static cVector Osds; - cBitmap *savedRegion; + bool isTrueColor; + cBitmap *savedBitmap; cBitmap *bitmaps[MAXOSDAREAS]; int numBitmaps; + cPixmapMemory *savedPixmap; + cPixmap *pixmaps[MAXOSDPIXMAPS]; + int numPixmaps; int left, top, width, height; uint level; bool active; @@ -295,6 +728,32 @@ protected: virtual void SetActive(bool On) { active = On; } ///< Sets this OSD to be the active one. ///< A derived class must call cOsd::SetActive(On). + const cPixmap * const *Pixmaps(void) { return pixmaps; } + ///< Returns the list of currently active pixmaps in this OSD. + int NumPixmaps(void) { return numPixmaps; } + ///< Returns the number of currently active pixmaps in this OSD. + cPixmap *AddPixmap(cPixmap *Pixmap); + ///< Adds the given Pixmap to the list of currently active pixmaps in this OSD. + ///< Returns Pixmap if the operation was successful, or NULL if the maximum + ///< number of pixmaps has been exceeded. + ///< A derived class that implements its own cPixmap class must call AddPixmap() + ///< in order to add a newly created pixmap to the OSD's list of pixmaps. + cPixmapMemory *RenderPixmaps(void); + ///< Renders the dirty part of all pixmaps into a resulting pixmap that + ///< shall be displayed on the OSD. The returned pixmap's view port is + ///< set to the location of the rectangle on the OSD that needs to be + ///< refreshed; its draw port's origin is at (0, 0), and it has the same + ///< size as the view port. + ///< If there are several non-overlapping dirty rectangles from different pixmaps, + ///< they are returned separately in order to avoid re-rendering large parts + ///< of the OSD that haven't changed at all. The caller must therefore call + ///< RenderPixmaps() repeatedly until it returns NULL, and display the returned + ///< parts of the OSD at their appropriate locations. During this entire + ///< operation the caller must hold a lock on the cPixmap mutex (for instance + ///< by putting a LOCK_PIXMAPS into the scope of the operation). + ///< If there are no dirty pixmaps, or if this is not a true color OSD, + ///< this function returns NULL. + ///< The caller must delete the returned pixmap after use. public: virtual ~cOsd(); ///< Shuts down the OSD. @@ -309,6 +768,9 @@ public: ///< screen. static int IsOpen(void) { return Osds.Size() && Osds[0]->level == OSD_LEVEL_DEFAULT; } ///< Returns true if there is currently a level 0 OSD open. + bool IsTrueColor(void) const { return isTrueColor; } + ///< Returns 'true' if this is a true color OSD (providing full 32 bit color + ///< depth). int Left(void) { return left; } int Top(void) { return top; } int Width(void) { return width; } @@ -323,9 +785,33 @@ public: ///< a single color combination, and may not be able to serve all ///< requested colors. By default the palette assumes there will be ///< 10 fixed colors and 10 color combinations. + ///< If this is a true color OSD, this function does nothing. cBitmap *GetBitmap(int Area); ///< Returns a pointer to the bitmap for the given Area, or NULL if no ///< such bitmap exists. + ///< If this is a true color OSD, a pointer to a dummy bitmap with 8bpp + ///< is returned. This is done so that skins that call this function + ///< in order to preset the bitmap's palette won't crash. + virtual cPixmap *CreatePixmap(int Layer, const cRect &ViewPort, const cRect &DrawPort = cRect::Null); + ///< Creates a new true color pixmap on this OSD (see cPixmap for details). + ///< The caller must not delete the returned object, it will be deleted when + ///< the OSD is deleted. DestroyPixmap() can be called if a pixmap shall be + ///< destroyed before the OSD is deleted. + ///< If this is not a true color OSD, this function returns NULL. + virtual void DestroyPixmap(cPixmap *Pixmap); + ///< Destroys the given Pixmap, which has previously been created by a call to + ///< CreatePixmap(). When the OSD is deleted, all pixmaps are destroyed + ///< automatically. So this function only needs to be used if a pixmap shall + ///< be destroyed while the OSD is still being used. + virtual void DrawImage(const cPoint &Point, const cImage &Image); + ///< Draws the given Image on this OSD at the given Point. + ///< If this is not a true color OSD, this function does nothing. + virtual void DrawImage(const cPoint &Point, int ImageHandle); + ///< Draws the image referenced by the given ImageHandle on this OSD at + ///< the given Point. ImageHandle must be a value that has previously been + ///< returned by a call to cOsdProvider::StoreImage(). If ImageHandle + ///< has an invalid value, nothing happens. + ///< If this is not a true color OSD, this function does nothing. virtual eOsdError CanHandleAreas(const tArea *Areas, int NumAreas); ///< Checks whether the OSD can display the given set of sub-areas. ///< The return value indicates whether a call to SetAreas() with this @@ -343,6 +829,9 @@ public: ///< are part of the rectangle that surrounds a given drawing operation ///< will be drawn into, with the proper offsets. ///< A new call overwrites any previous settings + ///< To set up a true color OSD, exactly one area must be requested, with + ///< its coordinates set to the full area the OSD shall cover, and the + ///< bpp value set to 32. virtual void SaveRegion(int x1, int y1, int x2, int y2); ///< Saves the region defined by the given coordinates for later restoration ///< through RestoreRegion(). Only one saved region can be active at any @@ -352,6 +841,7 @@ public: ///< If SaveRegion() has not been called before, nothing will happen. virtual eOsdError SetPalette(const cPalette &Palette, int Area); ///< Sets the Palette for the given Area (the first area is numbered 0). + ///< If this is a true color OSD, nothing happens and oeOk is returned. virtual void DrawPixel(int x, int y, tColor Color); ///< Sets the pixel at the given coordinates to the given Color, which is ///< a full 32 bit ARGB value. @@ -368,6 +858,7 @@ public: ///< area shall have its palette replaced with the one from Bitmap. ///< If Overlay is true, any pixel in Bitmap that has color index 0 will ///< not overwrite the corresponding pixel in the target area. + ///< If this is a true color OSD, ReplacePalette has no meaning. virtual void DrawText(int x, int y, const char *s, tColor ColorFg, tColor ColorBg, const cFont *Font, int Width = 0, int Height = 0, int Alignment = taDefault); ///< Draws the given string at coordinates (x, y) with the given foreground ///< and background color and font. If Width and Height are given, the text @@ -401,18 +892,40 @@ public: ///< 7: vertical, falling, upper virtual void Flush(void); ///< Actually commits all data to the OSD hardware. + ///< Flush() should return as soon as possible. }; +#define MAXOSDIMAGES 64 + class cOsdProvider { + friend class cPixmapMemory; private: static cOsdProvider *osdProvider; static int oldWidth; static int oldHeight; static double oldAspect; + static cImage *images[MAXOSDIMAGES]; protected: virtual cOsd *CreateOsd(int Left, int Top, uint Level) = 0; ///< Returns a pointer to a newly created cOsd object, which will be located ///< at the given coordinates. + virtual bool ProvidesTrueColor(void) { return false; } + ///< Returns true if this OSD provider is able to handle a true color OSD. + virtual int StoreImageData(const cImage &Image); + ///< Copies the given Image and returns a handle for later reference. + ///< A derived class can implement its own image storing mechanism by + ///< reimplementing this function as well as DropImageData(). + ///< The base class implementation simply copies the image data to allow + ///< plugins to always use this interface, no matter if the actual device + ///< provides support for storing image data or not. The handles returned + ///< by the default implementation are positive integers. A derived class + ///< might want to use negative integers as handles, so that it can fall + ///< back to using the base class image storing mechanism if, e.g., it runs + ///< out of memory. + virtual void DropImageData(int ImageHandle); + ///< Drops the image data referenced by ImageHandle. + static const cImage *GetImageData(int ImageHandle); + ///< Gets the image data referenced by ImageHandle. public: cOsdProvider(void); //XXX maybe parameter to make this one "sticky"??? (frame-buffer etc.) @@ -427,7 +940,22 @@ public: ///< Inquires the actual size of the video display and adjusts the OSD and ///< font sizes accordingly. If Force is true, all settings are recalculated, ///< even if the video resolution hasn't changed since the last call to - ///< this funtion. + ///< this function. + static bool SupportsTrueColor(void); + ///< Returns true if the current OSD provider is able to handle a true color OSD. + static int StoreImage(const cImage &Image); + ///< Stores the given Image for later use with DrawImage() on an OSD or + ///< pixmap. The returned number is a handle that must be used when + ///< referencing this image in a call to DrawImage() or DropImage(). + ///< The image data is copied, so any later changes to Image will have + ///< no effect on the stored image. + ///< A derived class may be able to copy frequently used images to some + ///< space where they can be retrieved faster than using a cImage in each call. + ///< If this is not a true color OSD, or if the image data can't be stored for + ///< any reason, this function returns 0 and nothing is stored. + static void DropImage(int ImageHandle); + ///< Drops the image referenced by the given ImageHandle. If ImageHandle + ///< has an invalid value, nothing happens. static void Shutdown(void); ///< Shuts down the OSD provider facility by deleting the current OSD provider. }; diff --git a/skinsttng.c b/skinsttng.c index c39ecce0..9bd1b15c 100644 --- a/skinsttng.c +++ b/skinsttng.c @@ -4,7 +4,7 @@ * See the main source file 'vdr.c' for copyright information and * how to reach the author. * - * $Id: skinsttng.c 2.6 2010/11/07 15:10:08 kls Exp $ + * $Id: skinsttng.c 2.7 2011/02/20 13:02:49 kls Exp $ */ // Star Trek: The Next Generation® is a registered trademark of Paramount Pictures @@ -182,12 +182,17 @@ cSkinSTTNGDisplayChannel::cSkinSTTNGDisplayChannel(bool WithInfo) int yt = (y0 + y1) / 2; int yb = (y6 + y7) / 2; osd = cOsdProvider::NewOsd(cOsd::OsdLeft(), cOsd::OsdTop() + (Setup.ChannelInfoPos ? 0 : cOsd::OsdHeight() - y7)); - tArea Areas[] = { { 0, 0, x7 - 1, y7 - 1, 8 } }; - if (Setup.AntiAlias && osd->CanHandleAreas(Areas, sizeof(Areas) / sizeof(tArea)) == oeOk) + tArea Areas[] = { { 0, 0, x7 - 1, y7 - 1, 32 } }; // TrueColor + if (osd->CanHandleAreas(Areas, sizeof(Areas) / sizeof(tArea)) == oeOk) osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea)); else { - tArea Areas[] = { { 0, 0, x7 - 1, y7 - 1, 4 } }; - osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea)); + tArea Areas[] = { { 0, 0, x7 - 1, y7 - 1, 8 } }; // 256 colors + if (Setup.AntiAlias && osd->CanHandleAreas(Areas, sizeof(Areas) / sizeof(tArea)) == oeOk) + osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea)); + else { + tArea Areas[] = { { 0, 0, x7 - 1, y7 - 1, 4 } }; // 16 colors + osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea)); + } } osd->DrawRectangle(x0, y0, x7 - 1, y7 - 1, Theme.Color(clrBackground)); osd->DrawRectangle(x0, y0, x1 - 1, y1 - 1, clrTransparent); @@ -220,12 +225,17 @@ cSkinSTTNGDisplayChannel::cSkinSTTNGDisplayChannel(bool WithInfo) y0 = 0; y1 = lineHeight; osd = cOsdProvider::NewOsd(cOsd::OsdLeft(), cOsd::OsdTop() + (Setup.ChannelInfoPos ? 0 : cOsd::OsdHeight() - y1)); - tArea Areas[] = { { x0, y0, x7 - 1, y1 - 1, 8 } }; - if (Setup.AntiAlias && osd->CanHandleAreas(Areas, sizeof(Areas) / sizeof(tArea)) == oeOk) + tArea Areas[] = { { x0, y0, x7 - 1, y1 - 1, 32 } }; // TrueColor + if (osd->CanHandleAreas(Areas, sizeof(Areas) / sizeof(tArea)) == oeOk) osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea)); else { - tArea Areas[] = { { x0, y0, x7 - 1, y1 - 1, 4 } }; - osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea)); + tArea Areas[] = { { x0, y0, x7 - 1, y1 - 1, 8 } }; // 256 colors + if (Setup.AntiAlias && osd->CanHandleAreas(Areas, sizeof(Areas) / sizeof(tArea)) == oeOk) + osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea)); + else { + tArea Areas[] = { { x0, y0, x7 - 1, y1 - 1, 4 } }; // 16 colors + osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea)); + } } osd->DrawRectangle(x0, y0, x7 - 1, y1 - 1, clrTransparent); osd->DrawEllipse (x0, y0, x1 - 1, y1 - 1, frameColor, 7); @@ -397,21 +407,26 @@ cSkinSTTNGDisplayMenu::cSkinSTTNGDisplayMenu(void) int yt = (y0 + y1) / 2; int yb = (y6 + y7) / 2; osd = cOsdProvider::NewOsd(cOsd::OsdLeft(), cOsd::OsdTop()); - tArea Areas[] = { { x0, y0, x7 - 1, y7 - 1, 8 } }; - if (Setup.AntiAlias && osd->CanHandleAreas(Areas, sizeof(Areas) / sizeof(tArea)) == oeOk) + tArea Areas[] = { { x0, y0, x7 - 1, y7 - 1, 32 } }; // TrueColor + if (osd->CanHandleAreas(Areas, sizeof(Areas) / sizeof(tArea)) == oeOk) osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea)); else { - tArea Areas[] = { { x0, y0, x7 - 1, y7 - 1, 4 } }; - if (osd->CanHandleAreas(Areas, sizeof(Areas) / sizeof(tArea)) == oeOk) + tArea Areas[] = { { x0, y0, x7 - 1, y7 - 1, 8 } }; // 256 colors + if (Setup.AntiAlias && osd->CanHandleAreas(Areas, sizeof(Areas) / sizeof(tArea)) == oeOk) osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea)); else { - tArea Areas[] = { { x0, y0, x7 - 1, y3 - 1, 2 }, - { x0, y3, x3 - 1, y4 - 1, 1 }, - { x3, y3, x4 - 1, y4 - 1, 2 }, - { x4, y3, x7 - 1, y4 - 1, 2 }, - { x0, y4, x7 - 1, y7 - 1, 4 } - }; - osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea)); + tArea Areas[] = { { x0, y0, x7 - 1, y7 - 1, 4 } }; // 16 colors + if (osd->CanHandleAreas(Areas, sizeof(Areas) / sizeof(tArea)) == oeOk) + osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea)); + else { + tArea Areas[] = { { x0, y0, x7 - 1, y3 - 1, 2 }, // 2..16 colors + { x0, y3, x3 - 1, y4 - 1, 1 }, + { x3, y3, x4 - 1, y4 - 1, 2 }, + { x4, y3, x7 - 1, y4 - 1, 2 }, + { x0, y4, x7 - 1, y7 - 1, 4 } + }; + osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea)); + } } } osd->DrawRectangle(x0, y0, x7 - 1, y7 - 1, Theme.Color(clrBackground)); @@ -754,12 +769,17 @@ cSkinSTTNGDisplayReplay::cSkinSTTNGDisplayReplay(bool ModeOnly) int yt = (y0 + y1) / 2; int yb = (y6 + y7) / 2; osd = cOsdProvider::NewOsd(cOsd::OsdLeft(), cOsd::OsdTop() + cOsd::OsdHeight() - y7); - tArea Areas[] = { { 0, 0, x7 - 1, y7 - 1, 8 } }; - if (Setup.AntiAlias && osd->CanHandleAreas(Areas, sizeof(Areas) / sizeof(tArea)) == oeOk) + tArea Areas[] = { { 0, 0, x7 - 1, y7 - 1, 32 } }; // TrueColor + if (osd->CanHandleAreas(Areas, sizeof(Areas) / sizeof(tArea)) == oeOk) osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea)); else { - tArea Areas[] = { { 0, 0, x7 - 1, y7 - 1, 4 } }; - osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea)); + tArea Areas[] = { { 0, 0, x7 - 1, y7 - 1, 8 } }; // 256 colors + if (Setup.AntiAlias && osd->CanHandleAreas(Areas, sizeof(Areas) / sizeof(tArea)) == oeOk) + osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea)); + else { + tArea Areas[] = { { 0, 0, x7 - 1, y7 - 1, 4 } }; // 16 colors + osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea)); + } } osd->DrawRectangle(x0, y0, x7 - 1, y7 - 1, ModeOnly ? clrTransparent : Theme.Color(clrBackground)); if (!ModeOnly) { @@ -888,12 +908,17 @@ cSkinSTTNGDisplayVolume::cSkinSTTNGDisplayVolume(void) y0 = 0; y1 = lineHeight; osd = cOsdProvider::NewOsd(cOsd::OsdLeft(), cOsd::OsdTop() + cOsd::OsdHeight() - y1); - tArea Areas[] = { { x0, y0, x7 - 1, y1 - 1, 8 } }; - if (Setup.AntiAlias && osd->CanHandleAreas(Areas, sizeof(Areas) / sizeof(tArea)) == oeOk) + tArea Areas[] = { { x0, y0, x7 - 1, y1 - 1, 32 } }; // TrueColor + if (osd->CanHandleAreas(Areas, sizeof(Areas) / sizeof(tArea)) == oeOk) osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea)); else { - tArea Areas[] = { { x0, y0, x7 - 1, y1 - 1, 4 } }; - osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea)); + tArea Areas[] = { { x0, y0, x7 - 1, y1 - 1, 8 } }; // 256 colors + if (Setup.AntiAlias && osd->CanHandleAreas(Areas, sizeof(Areas) / sizeof(tArea)) == oeOk) + osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea)); + else { + tArea Areas[] = { { x0, y0, x7 - 1, y1 - 1, 4 } }; // 16 colors + osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea)); + } } osd->DrawRectangle(x0, y0, x7 - 1, y1 - 1, clrTransparent); osd->DrawEllipse (x0, y0, x1 - 1, y1 - 1, frameColor, 7); @@ -1003,21 +1028,26 @@ cSkinSTTNGDisplayTracks::cSkinSTTNGDisplayTracks(const char *Title, int NumTrack int yt = (y0 + y1) / 2; int yb = (y6 + y7) / 2; osd = cOsdProvider::NewOsd(cOsd::OsdLeft(), cOsd::OsdTop() + cOsd::OsdHeight() - y7); - tArea Areas[] = { { x0, y0, x7 - 1, y7 - 1, 8 } }; - if (Setup.AntiAlias && osd->CanHandleAreas(Areas, sizeof(Areas) / sizeof(tArea)) == oeOk) + tArea Areas[] = { { x0, y0, x7 - 1, y7 - 1, 32 } }; // TrueColor + if (osd->CanHandleAreas(Areas, sizeof(Areas) / sizeof(tArea)) == oeOk) osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea)); else { - tArea Areas[] = { { x0, y0, x7 - 1, y7 - 1, 4 } }; - if (osd->CanHandleAreas(Areas, sizeof(Areas) / sizeof(tArea)) == oeOk) + tArea Areas[] = { { x0, y0, x7 - 1, y7 - 1, 8 } }; // 256 colors + if (Setup.AntiAlias && osd->CanHandleAreas(Areas, sizeof(Areas) / sizeof(tArea)) == oeOk) osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea)); else { - tArea Areas[] = { { x0, y0, x7 - 1, y3 - 1, 2 }, - { x0, y3, x3 - 1, y4 - 1, 1 }, - { x3, y3, x4 - 1, y4 - 1, 2 }, - { x4, y3, x7 - 1, y4 - 1, 2 }, - { x0, y4, x7 - 1, y7 - 1, 4 } - }; - osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea)); + tArea Areas[] = { { x0, y0, x7 - 1, y7 - 1, 4 } }; // 16 colors + if (osd->CanHandleAreas(Areas, sizeof(Areas) / sizeof(tArea)) == oeOk) + osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea)); + else { + tArea Areas[] = { { x0, y0, x7 - 1, y3 - 1, 2 }, // 2..16 colors + { x0, y3, x3 - 1, y4 - 1, 1 }, + { x3, y3, x4 - 1, y4 - 1, 2 }, + { x4, y3, x7 - 1, y4 - 1, 2 }, + { x0, y4, x7 - 1, y7 - 1, 4 } + }; + osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea)); + } } } osd->DrawRectangle(x0, y0, x7 - 1, y7 - 1, Theme.Color(clrBackground)); @@ -1131,12 +1161,17 @@ cSkinSTTNGDisplayMessage::cSkinSTTNGDisplayMessage(void) y0 = 0; y1 = lineHeight; osd = cOsdProvider::NewOsd(cOsd::OsdLeft(), cOsd::OsdTop() + cOsd::OsdHeight() - y1); - tArea Areas[] = { { x0, y0, x7 - 1, y1 - 1, 8 } }; - if (Setup.AntiAlias && osd->CanHandleAreas(Areas, sizeof(Areas) / sizeof(tArea)) == oeOk) + tArea Areas[] = { { x0, y0, x7 - 1, y1 - 1, 32 } }; // TrueColor + if (osd->CanHandleAreas(Areas, sizeof(Areas) / sizeof(tArea)) == oeOk) osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea)); else { - tArea Areas[] = { { x0, y0, x7 - 1, y1 - 1, 2 } }; - osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea)); + tArea Areas[] = { { x0, y0, x7 - 1, y1 - 1, 8 } }; // 256 colors + if (Setup.AntiAlias && osd->CanHandleAreas(Areas, sizeof(Areas) / sizeof(tArea)) == oeOk) + osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea)); + else { + tArea Areas[] = { { x0, y0, x7 - 1, y1 - 1, 2 } }; // 4 colors + osd->SetAreas(Areas, sizeof(Areas) / sizeof(tArea)); + } } osd->DrawRectangle(x0, y0, x7 - 1, y1 - 1, clrTransparent); osd->DrawEllipse (x0, y0, x1 - 1, y1 - 1, frameColor, 7);