From 25fb0e67fdb540a8fa867b3dbbe40ae7207e903a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20A=C3=9Fmus?= Date: Sun, 26 Sep 2010 20:24:04 +0000 Subject: [PATCH] Added SubtitleBitmap class which can create a transparent BBitmap from a subtitle line as found in SRT files. It supports all tags except positioning (should probably be handled in upper layers anyway). The parsing is simplistic but should be robust. The layout of text is handled by an offsreen BTextView. VideoView can be given a subtitle string. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@38822 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- src/apps/mediaplayer/Jamfile | 1 + src/apps/mediaplayer/VideoView.cpp | 45 ++- src/apps/mediaplayer/VideoView.h | 8 + .../mediaplayer/interface/SubtitleBitmap.cpp | 308 ++++++++++++++++++ .../mediaplayer/interface/SubtitleBitmap.h | 41 +++ 5 files changed, 400 insertions(+), 3 deletions(-) create mode 100644 src/apps/mediaplayer/interface/SubtitleBitmap.cpp create mode 100644 src/apps/mediaplayer/interface/SubtitleBitmap.h 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