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:
parent
dce7934b8d
commit
25fb0e67fd
@ -28,6 +28,7 @@ Application MediaPlayer :
|
||||
PlayPauseButton.cpp
|
||||
PositionToolTip.cpp
|
||||
SeekSlider.cpp
|
||||
SubtitleBitmap.cpp
|
||||
SymbolButton.cpp
|
||||
TransportControlGroup.cpp
|
||||
VolumeSlider.cpp
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
308
src/apps/mediaplayer/interface/SubtitleBitmap.cpp
Normal file
308
src/apps/mediaplayer/interface/SubtitleBitmap.cpp
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
|
41
src/apps/mediaplayer/interface/SubtitleBitmap.h
Normal file
41
src/apps/mediaplayer/interface/SubtitleBitmap.h
Normal 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
|
Loading…
Reference in New Issue
Block a user