* 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>
#include "StackBlurFilter.h"
SubtitleBitmap::SubtitleBitmap()
:
fBitmap(NULL),
fTextView(new BTextView("offscreen text")),
fShadowTextView(new BTextView("offscreen text shadow")),
fCharsPerLine(36),
fUseSoftShadow(true),
fOverlayMode(false)
{
fTextView->SetStylable(true);
fTextView->MakeEditable(false);
fTextView->SetAlignment(B_ALIGN_CENTER);
fShadowTextView->SetStylable(true);
fShadowTextView->MakeEditable(false);
fShadowTextView->SetAlignment(B_ALIGN_CENTER);
}
SubtitleBitmap::~SubtitleBitmap()
{
delete fBitmap;
delete fTextView;
delete fShadowTextView;
}
bool
SubtitleBitmap::SetText(const char* text)
{
if (text == fText)
return false;
fText = text;
_GenerateBitmap();
return true;
}
void
SubtitleBitmap::SetVideoBounds(BRect bounds)
{
if (bounds == fVideoBounds)
return;
fVideoBounds = bounds;
fUseSoftShadow = true;
_GenerateBitmap();
}
void
SubtitleBitmap::SetOverlayMode(bool overlayMode)
{
if (overlayMode == fOverlayMode)
return;
fOverlayMode = overlayMode;
_GenerateBitmap();
}
void
SubtitleBitmap::SetCharsPerLine(float charsPerLine)
{
if (charsPerLine == fCharsPerLine)
return;
fCharsPerLine = charsPerLine;
fUseSoftShadow = true;
_GenerateBitmap();
}
const BBitmap*
SubtitleBitmap::Bitmap() const
{
return fBitmap;
}
void
SubtitleBitmap::_GenerateBitmap()
{
if (!fVideoBounds.IsValid())
return;
delete fBitmap;
BRect bounds;
float outlineRadius;
_InsertText(bounds, outlineRadius, fOverlayMode);
bigtime_t startTime = 0;
if (!fOverlayMode && fUseSoftShadow)
startTime = system_time();
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();
if (!fOverlayMode && fUseSoftShadow) {
fShadowTextView->Sync();
StackBlurFilter filter;
filter.Filter(fBitmap, outlineRadius * 2);
}
fShadowTextView->RemoveSelf();
fBitmap->AddChild(fTextView);
fTextView->ResizeTo(bounds.Width(), bounds.Height());
if (!fOverlayMode && fUseSoftShadow)
fTextView->MoveTo(-outlineRadius / 2, -outlineRadius / 2);
else
fTextView->MoveTo(0, 0);
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();
}
if (!fOverlayMode && fUseSoftShadow && system_time() - startTime > 10000)
fUseSoftShadow = false;
}
struct ParseState {
ParseState(rgb_color color)
:
color(color),
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 startPos = tagPos;
tagPos = string.Length();
tagLength = 0;
BString tag;
for (int32 i = 0; i < kTagCount; i++) {
int32 nextTag = string.IFindFirst(kTags[i], startPos);
if (nextTag >= startPos && nextTag < tagPos) {
tagPos = nextTag;
tag = kTags[i];
}
}
if (tag.Length() == 0)
return false;
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>") {
if (state->previous != NULL) {
ParseState* oldState = state;
state = state->previous;
delete oldState;
}
}
return true;
}
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,
const rgb_color& color, bool changeColor)
{
ParseState rootState(color);
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);
}
while (state->previous != NULL) {
ParseState* oldState = state;
state = state->previous;
delete oldState;
}
}
void
SubtitleBitmap::_InsertText(BRect& textRect, float& outlineRadius,
bool overlayMode)
{
BFont font(be_plain_font);
float fontSize = ceilf((fVideoBounds.Width() * 0.9) / fCharsPerLine);
outlineRadius = 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;
textRect = fVideoBounds;
textRect.OffsetBy(outlineRadius, outlineRadius);
fTextView->SetText(NULL);
fTextView->SetFontAndColor(&font, B_FONT_ALL, &color);
fTextView->ResizeTo(fVideoBounds.Width(), fVideoBounds.Height());
fTextView->Insert(" ");
parse_text(fText, fTextView, font, color, true);
font.SetFalseBoldWidth(outlineRadius);
fShadowTextView->ForceFontAliasing(overlayMode);
fShadowTextView->SetText(NULL);
fShadowTextView->SetFontAndColor(&font, B_FONT_ALL, &shadow);
fShadowTextView->ResizeTo(fVideoBounds.Width(), fVideoBounds.Height());
fShadowTextView->Insert(" ");
parse_text(fText, fShadowTextView, font, shadow, false);
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(-outlineRadius, -outlineRadius);
textRect.OffsetTo(B_ORIGIN);
textRect.bottom = outlineRadius;
int32 lineCount = fTextView->CountLines();
for (int32 i = 0; i < lineCount; i++)
textRect.bottom += fTextView->LineHeight(i);
textRect.bottom += outlineRadius;
}