diff options
author | Klaus Schmidinger <vdr@tvdr.de> | 2011-02-20 15:12:56 +0100 |
---|---|---|
committer | Klaus Schmidinger <vdr@tvdr.de> | 2011-02-20 15:12:56 +0100 |
commit | 6c7089afd257df3a9033a5bd2f205b8f03eccfaf (patch) | |
tree | d146c9d9c4b37d52b966607d7c1ef9e3b85dcf0f /osd.c | |
parent | 343071cc6ab69169312ae04a7888cd55a79395d0 (diff) | |
download | vdr-6c7089afd257df3a9033a5bd2f205b8f03eccfaf.tar.gz vdr-6c7089afd257df3a9033a5bd2f205b8f03eccfaf.tar.bz2 |
Implemented support for TrueColor OSD
Diffstat (limited to 'osd.c')
-rw-r--r-- | osd.c | 1098 |
1 files changed, 1067 insertions, 31 deletions
@@ -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 <rnissl@gmx.de>) +// 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<const cPixmapMemory *>(Pixmap)) { + cRect s = Source.Intersected(Pixmap->DrawPort().Size()); + if (!s.IsEmpty()) { + cPoint v = Dest - Source.Point(); + cRect d = s.Shifted(v).Intersected(DrawPort().Size()); + if (!d.IsEmpty()) { + s = d.Shifted(-v); + int a = pm->Alpha(); + int ws = pm->DrawPort().Width(); + int wd = DrawPort().Width(); + const tColor *ps = pm->data + ws * s.Top() + s.Left(); + tColor *pd = data + wd * d.Top() + d.Left(); + for (int y = d.Height(); y-- > 0; ) { + const tColor *cs = ps; + tColor *cd = pd; + for (int x = d.Width(); x-- > 0; ) { + *cd = AlphaBlend(*cs, *cd, a); + cs++; + cd++; + } + ps += ws; + pd += wd; + } + MarkDrawPortDirty(d); + } + } + } + } + Unlock(); +} + +void cPixmapMemory::Copy(const cPixmap *Pixmap, const cRect &Source, const cPoint &Dest) +{ + Lock(); + if (const cPixmapMemory *pm = dynamic_cast<const cPixmapMemory *>(Pixmap)) { + cRect s = Source.Intersected(pm->DrawPort().Size()); + if (!s.IsEmpty()) { + cPoint v = Dest - Source.Point(); + cRect d = s.Shifted(v).Intersected(DrawPort().Size()); + if (!d.IsEmpty()) { + s = d.Shifted(-v); + int ws = pm->DrawPort().Width(); + int wd = DrawPort().Width(); + int w = d.Width() * sizeof(tColor); + const tColor *ps = pm->data + ws * s.Top() + s.Left(); + tColor *pd = data + wd * d.Top() + d.Left(); + for (int y = d.Height(); y-- > 0; ) { + memcpy(pd, ps, w); + ps += ws; + pd += wd; + } + MarkDrawPortDirty(d); + } + } + } + Unlock(); +} + +void cPixmapMemory::Scroll(const cPoint &Dest, const cRect &Source) +{ + Lock(); + cRect s; + if (&Source == &cRect::Null) + s = DrawPort().Shifted(-DrawPort().Point()); + else + s = Source.Intersected(DrawPort().Size()); + if (!s.IsEmpty()) { + cPoint v = Dest - Source.Point(); + cRect d = s.Shifted(v).Intersected(DrawPort().Size()); + if (!d.IsEmpty()) { + s = d.Shifted(-v); + if (d.Point() != s.Point()) { + int ws = DrawPort().Width(); + int wd = ws; + int w = d.Width() * sizeof(tColor); + const tColor *ps = data + ws * s.Top() + s.Left(); + tColor *pd = data + wd * d.Top() + d.Left(); + for (int y = d.Height(); y-- > 0; ) { + memmove(pd, ps, w); // source and destination might overlap! + ps += ws; + pd += wd; + } + if (panning) + SetDrawPortPoint(DrawPort().Point().Shifted(s.Point() - d.Point()), false); + else + MarkDrawPortDirty(d); + } + } + } + Unlock(); +} + +void cPixmapMemory::Pan(const cPoint &Dest, const cRect &Source) +{ + Lock(); + panning = true; + Scroll(Dest, Source); + panning = false; + Unlock(); +} + // --- cOsd ------------------------------------------------------------------ static const char *OsdErrorTexts[] = { @@ -745,8 +1556,11 @@ cVector<cOsd *> 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; |