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
This commit is contained in:
Stephan Aßmus 2010-09-26 20:24:04 +00:00
parent dce7934b8d
commit 25fb0e67fd
5 changed files with 400 additions and 3 deletions

View File

@ -28,6 +28,7 @@ Application MediaPlayer :
PlayPauseButton.cpp
PositionToolTip.cpp
SeekSlider.cpp
SubtitleBitmap.cpp
SymbolButton.cpp
TransportControlGroup.cpp
VolumeSlider.cpp

View File

@ -15,6 +15,7 @@
#include <WindowScreen.h>
#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("<b><i>This</i></b> is a <font color=\"#00ff00\">test</font>!"
// "\nWith a <i>short</i> line and a <b>long</b> 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);
}
}

View File

@ -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

View File

@ -0,0 +1,308 @@
/*
* Copyright 2010, Stephan Aßmus <superstippi@gmx.de>.
* Distributed under the terms of the MIT License.
*/
#include "SubtitleBitmap.h"
#include <stdio.h>
#include <Bitmap.h>
#include <TextView.h>
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[] = {
"<b>", "</b>",
"<i>", "</i>",
"<u>", "</u>",
"<font color=\"#", "</font>"
};
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 == "<b>") {
state = new ParseState(state);
state->bold = true;
} else if (tag == "<i>") {
state = new ParseState(state);
state->italic = true;
} else if (tag == "<u>") {
state = new ParseState(state);
state->underlined = true;
} else if (tag == "<font color=\"#") {
state = new ParseState(state);
char number[16];
snprintf(number, sizeof(number), "0x%.6s",
string.String() + tagPos + tag.Length());
int colorInt;
if (sscanf(number, "%x", &colorInt) == 1) {
state->color.red = (colorInt & 0xff0000) >> 16;
state->color.green = (colorInt & 0x00ff00) >> 8;
state->color.blue = (colorInt & 0x0000ff);
tagLength += 8;
}
} else if (tag == "</b>" || tag == "</i>" || tag == "</u>"
|| tag == "</font>") {
// 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;
}

View File

@ -0,0 +1,41 @@
/*
* Copyright 2010, Stephan Aßmus <superstippi@gmx.de>.
* Distributed under the terms of the MIT License.
*/
#ifndef SUBTITLE_BITMAP_H
#define SUBTITLE_BITMAP_H
#include <Rect.h>
#include <String.h>
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