diff --git a/src/apps/mediaplayer/Jamfile b/src/apps/mediaplayer/Jamfile index 43ccf8b6e0..ea9d37bb20 100644 --- a/src/apps/mediaplayer/Jamfile +++ b/src/apps/mediaplayer/Jamfile @@ -28,6 +28,7 @@ Application MediaPlayer : PlayPauseButton.cpp PositionToolTip.cpp SeekSlider.cpp + SubtitleBitmap.cpp SymbolButton.cpp TransportControlGroup.cpp VolumeSlider.cpp diff --git a/src/apps/mediaplayer/VideoView.cpp b/src/apps/mediaplayer/VideoView.cpp index 24638f45c9..fa72ea6d4b 100644 --- a/src/apps/mediaplayer/VideoView.cpp +++ b/src/apps/mediaplayer/VideoView.cpp @@ -15,6 +15,7 @@ #include #include "Settings.h" +#include "SubtitleBitmap.h" VideoView::VideoView(BRect frame, const char* name, uint32 resizeMask) @@ -26,7 +27,9 @@ VideoView::VideoView(BRect frame, const char* name, uint32 resizeMask) fIsPlaying(false), fIsFullscreen(false), fLastMouseMove(system_time()), - fGlobalSettingsListener(this) + fGlobalSettingsListener(this), + fSubtitleBitmap(new SubtitleBitmap), + fHasSubtitle(false) { SetViewColor(B_TRANSPARENT_COLOR); SetHighColor(0, 0, 0); @@ -40,12 +43,16 @@ VideoView::VideoView(BRect frame, const char* name, uint32 resizeMask) Settings::Default()->AddListener(&fGlobalSettingsListener); _AdoptGlobalSettings(); + +//SetSubtitle("This is a test!" +// "\nWith a short line and a long line."); } VideoView::~VideoView() { Settings::Default()->RemoveListener(&fGlobalSettingsListener); + delete fSubtitleBitmap; } @@ -177,8 +184,12 @@ VideoView::SetBitmap(const BBitmap* bitmap) ClearViewOverlay(); SetViewColor(B_TRANSPARENT_COLOR); } - if (!fOverlayMode) - _DrawBitmap(bitmap); + if (!fOverlayMode) { + if (fHasSubtitle) + Invalidate(fVideoFrame); + else + _DrawBitmap(bitmap); + } UnlockBitmap(); } @@ -273,6 +284,21 @@ VideoView::SetVideoFrame(const BRect& frame) Invalidate(&invalid); fVideoFrame = frame; + + fSubtitleBitmap->SetVideoBounds(fVideoFrame.OffsetToCopy(B_ORIGIN)); +} + + +void +VideoView::SetSubtitle(const char* text) +{ + if (text == NULL || text[0] == '\0') { + fHasSubtitle = false; + return; + } + + fHasSubtitle = true; + fSubtitleBitmap->SetText(text); } @@ -282,8 +308,21 @@ VideoView::SetVideoFrame(const BRect& frame) void VideoView::_DrawBitmap(const BBitmap* bitmap) { + SetDrawingMode(B_OP_COPY); uint32 options = fUseBilinearScaling ? B_FILTER_BITMAP_BILINEAR : 0; DrawBitmap(bitmap, bitmap->Bounds(), fVideoFrame, options); + + if (fHasSubtitle) { + SetDrawingMode(B_OP_ALPHA); + SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY); + + const BBitmap* subtitleBitmap = fSubtitleBitmap->Bitmap(); + BPoint offset; + offset.x = (fVideoFrame.left + fVideoFrame.right + - subtitleBitmap->Bounds().Width()) / 2; + offset.y = fVideoFrame.bottom - subtitleBitmap->Bounds().Height(); + DrawBitmap(subtitleBitmap, offset); + } } diff --git a/src/apps/mediaplayer/VideoView.h b/src/apps/mediaplayer/VideoView.h index 444a9549cd..bc7c2e95ec 100644 --- a/src/apps/mediaplayer/VideoView.h +++ b/src/apps/mediaplayer/VideoView.h @@ -17,6 +17,9 @@ enum { }; +class SubtitleBitmap; + + class VideoView : public BView, public VideoTarget { public: VideoView(BRect frame, const char* name, @@ -48,6 +51,8 @@ public: void SetFullscreen(bool fullScreen); void SetVideoFrame(const BRect& frame); + void SetSubtitle(const char* text); + private: void _DrawBitmap(const BBitmap* bitmap); void _AdoptGlobalSettings(); @@ -64,6 +69,9 @@ private: ListenerAdapter fGlobalSettingsListener; bool fUseOverlays; bool fUseBilinearScaling; + + SubtitleBitmap* fSubtitleBitmap; + bool fHasSubtitle; }; #endif // VIDEO_VIEW_H diff --git a/src/apps/mediaplayer/interface/SubtitleBitmap.cpp b/src/apps/mediaplayer/interface/SubtitleBitmap.cpp new file mode 100644 index 0000000000..c77c5bc4f2 --- /dev/null +++ b/src/apps/mediaplayer/interface/SubtitleBitmap.cpp @@ -0,0 +1,308 @@ +/* + * Copyright 2010, Stephan Aßmus . + * Distributed under the terms of the MIT License. + */ + + +#include "SubtitleBitmap.h" + +#include + +#include +#include + + +SubtitleBitmap::SubtitleBitmap() + : + fBitmap(NULL), + fTextView(new BTextView("offscreen text")), + fShadowTextView(new BTextView("offscreen text shadow")) +{ + fTextView->SetStylable(true); + fTextView->MakeEditable(false); + fTextView->SetWordWrap(false); + fTextView->SetAlignment(B_ALIGN_CENTER); + + fShadowTextView->SetStylable(true); + fShadowTextView->MakeEditable(false); + fShadowTextView->SetWordWrap(false); + fShadowTextView->SetAlignment(B_ALIGN_CENTER); +} + + +SubtitleBitmap::~SubtitleBitmap() +{ + delete fBitmap; + delete fTextView; + delete fShadowTextView; +} + + +void +SubtitleBitmap::SetText(const char* text) +{ + if (text == fText) + return; + + fText = text; + + _GenerateBitmap(); +} + + +void +SubtitleBitmap::SetVideoBounds(BRect bounds) +{ + if (bounds == fVideoBounds) + return; + + fVideoBounds = bounds; + + _GenerateBitmap(); +} + + +const BBitmap* +SubtitleBitmap::Bitmap() const +{ + return fBitmap; +} + + +void +SubtitleBitmap::_GenerateBitmap() +{ + if (!fVideoBounds.IsValid()) + return; + + delete fBitmap; + + BRect bounds = _InsertText(); + + fBitmap = new BBitmap(bounds, B_BITMAP_ACCEPTS_VIEWS, B_RGBA32); + memset(fBitmap->Bits(), 0, fBitmap->BitsLength()); + + if (fBitmap->Lock()) { + fBitmap->AddChild(fShadowTextView); + fShadowTextView->ResizeTo(bounds.Width(), bounds.Height()); + + fShadowTextView->SetViewColor(0, 0, 0, 0); + fShadowTextView->SetDrawingMode(B_OP_ALPHA); + fShadowTextView->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_COMPOSITE); + + fShadowTextView->PushState(); + fShadowTextView->Draw(bounds); + fShadowTextView->PopState(); + + fShadowTextView->Sync(); + fShadowTextView->RemoveSelf(); + + fBitmap->AddChild(fTextView); + fTextView->ResizeTo(bounds.Width(), bounds.Height()); + + fTextView->SetViewColor(0, 0, 0, 0); + fTextView->SetDrawingMode(B_OP_ALPHA); + fTextView->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_COMPOSITE); + + fTextView->PushState(); + fTextView->Draw(bounds); + fTextView->PopState(); + + fTextView->Sync(); + fTextView->RemoveSelf(); + + fBitmap->Unlock(); + } +} + + +struct ParseState { + ParseState() + : + color((rgb_color){ 255, 255, 255, 255 }), + bold(false), + italic(false), + underlined(false), + + previous(NULL) + { + } + + ParseState(ParseState* previous) + : + color(previous->color), + bold(previous->bold), + italic(previous->italic), + underlined(previous->underlined), + + previous(previous) + { + } + rgb_color color; + bool bold; + bool italic; + bool underlined; + + ParseState* previous; +}; + + +static bool +find_next_tag(const BString& string, int32& tagPos, int32& tagLength, + ParseState*& state) +{ + static const char* kTags[] = { + "", "", + "", "", + "", "", + "" + }; + static const int32 kTagCount = sizeof(kTags) / sizeof(const char*); + + int32 current = tagPos; + tagPos = string.Length(); + tagLength = 0; + + BString tag; + for (int32 i = 0; i < kTagCount; i++) { + int32 nextTag = string.FindFirst(kTags[i], current); + if (nextTag >= current && nextTag < tagPos) { + tagPos = nextTag; + tag = kTags[i]; + } + } + + if (tag != "") { + tagLength = tag.Length(); + if (tag == "") { + state = new ParseState(state); + state->bold = true; + } else if (tag == "") { + state = new ParseState(state); + state->italic = true; + } else if (tag == "") { + state = new ParseState(state); + state->underlined = true; + } else if (tag == "color.red = (colorInt & 0xff0000) >> 16; + state->color.green = (colorInt & 0x00ff00) >> 8; + state->color.blue = (colorInt & 0x0000ff); + tagLength += 8; + } + } else if (tag == "" || tag == "" || tag == "" + || tag == "") { + // Pop state + if (state->previous != NULL) { + ParseState* oldState = state; + state = state->previous; + delete oldState; + } + } + return true; + } + return false; +} + + +static void +apply_state(BTextView* textView, const ParseState* state, BFont font, + bool changeColor) +{ + uint16 face = 0; + if (state->bold || state->italic || state->underlined) { + if (state->bold) + face |= B_BOLD_FACE; + if (state->italic) + face |= B_ITALIC_FACE; + if (state->underlined) + face |= B_UNDERSCORE_FACE; + } else + face = B_REGULAR_FACE; + font.SetFace(face); + if (changeColor) + textView->SetFontAndColor(&font, B_FONT_ALL, &state->color); + else + textView->SetFontAndColor(&font, B_FONT_ALL, NULL); +} + + +static void +parse_text(const BString& string, BTextView* textView, const BFont& font, + bool changeColor) +{ + ParseState rootState; + ParseState* state = &rootState; + + int32 pos = 0; + while (pos < string.Length()) { + int32 nextPos = pos; + int32 tagLength; + bool stateChanged = find_next_tag(string, nextPos, tagLength, state); + if (nextPos > pos) { + BString subString; + string.CopyInto(subString, pos, nextPos - pos); + textView->Insert(subString.String()); + } + pos = nextPos + tagLength; + if (stateChanged) + apply_state(textView, state, font, changeColor); + } +} + + +BRect +SubtitleBitmap::_InsertText() +{ + BFont font(be_plain_font); + float fontSize = ceilf((fVideoBounds.Width() * 0.9) / 35); + float falseBoldWidth = ceilf(fontSize / 28.0); + font.SetSize(fontSize); + + rgb_color shadow; + shadow.red = 0; + shadow.green = 0; + shadow.blue = 0; + shadow.alpha = 200; + + rgb_color color; + color.red = 255; + color.green = 255; + color.blue = 255; + color.alpha = 240; + + BRect textRect = fVideoBounds; + textRect.OffsetBy(falseBoldWidth, falseBoldWidth); + + fTextView->SetText(NULL); + fTextView->SetFontAndColor(&font, B_FONT_ALL, &color); + + fTextView->Insert(" "); + parse_text(fText, fTextView, font, true); + + font.SetFalseBoldWidth(falseBoldWidth); + fShadowTextView->SetText(NULL); + fShadowTextView->SetFontAndColor(&font, B_FONT_ALL, &shadow); + + fShadowTextView->Insert(" "); + parse_text(fText, fShadowTextView, font, false); + + // This causes the BTextView to calculate the layout of the text + fTextView->SetTextRect(BRect(0, 0, 0, 0)); + fTextView->SetTextRect(textRect); + fShadowTextView->SetTextRect(BRect(0, 0, 0, 0)); + fShadowTextView->SetTextRect(textRect); + + textRect = fTextView->TextRect(); + textRect.InsetBy(-falseBoldWidth, -falseBoldWidth); + textRect.OffsetTo(B_ORIGIN); + return textRect; +} + + + diff --git a/src/apps/mediaplayer/interface/SubtitleBitmap.h b/src/apps/mediaplayer/interface/SubtitleBitmap.h new file mode 100644 index 0000000000..362e68579e --- /dev/null +++ b/src/apps/mediaplayer/interface/SubtitleBitmap.h @@ -0,0 +1,41 @@ +/* + * Copyright 2010, Stephan Aßmus . + * Distributed under the terms of the MIT License. + */ +#ifndef SUBTITLE_BITMAP_H +#define SUBTITLE_BITMAP_H + + +#include +#include + + +class BBitmap; +class BTextView; + + +class SubtitleBitmap { +public: + SubtitleBitmap(); + virtual ~SubtitleBitmap(); + + void SetText(const char* text); + void SetVideoBounds(BRect bounds); + + const BBitmap* Bitmap() const; + +private: + void _GenerateBitmap(); + BRect _InsertText(); + +private: + BBitmap* fBitmap; + BTextView* fTextView; + BTextView* fShadowTextView; + BString fText; + + BRect fVideoBounds; +}; + + +#endif // SUBTITLE_BITMAP_H