125fb0e67SStephan Aßmus /* 225fb0e67SStephan Aßmus * Copyright 2010, Stephan Aßmus <superstippi@gmx.de>. 325fb0e67SStephan Aßmus * Distributed under the terms of the MIT License. 425fb0e67SStephan Aßmus */ 525fb0e67SStephan Aßmus 625fb0e67SStephan Aßmus 725fb0e67SStephan Aßmus #include "SubtitleBitmap.h" 825fb0e67SStephan Aßmus 925fb0e67SStephan Aßmus #include <stdio.h> 1025fb0e67SStephan Aßmus 1125fb0e67SStephan Aßmus #include <Bitmap.h> 1225fb0e67SStephan Aßmus #include <TextView.h> 1325fb0e67SStephan Aßmus 14c8ccdf52SStephan Aßmus #include "StackBlurFilter.h" 15c8ccdf52SStephan Aßmus 1625fb0e67SStephan Aßmus 1725fb0e67SStephan Aßmus SubtitleBitmap::SubtitleBitmap() 1825fb0e67SStephan Aßmus : 1925fb0e67SStephan Aßmus fBitmap(NULL), 2025fb0e67SStephan Aßmus fTextView(new BTextView("offscreen text")), 21*01e0d327SStephan Aßmus fShadowTextView(new BTextView("offscreen text shadow")), 22*01e0d327SStephan Aßmus fUseSoftShadow(true), 23*01e0d327SStephan Aßmus fOverlayMode(false) 2425fb0e67SStephan Aßmus { 2525fb0e67SStephan Aßmus fTextView->SetStylable(true); 2625fb0e67SStephan Aßmus fTextView->MakeEditable(false); 2725fb0e67SStephan Aßmus fTextView->SetWordWrap(false); 2825fb0e67SStephan Aßmus fTextView->SetAlignment(B_ALIGN_CENTER); 2925fb0e67SStephan Aßmus 3025fb0e67SStephan Aßmus fShadowTextView->SetStylable(true); 3125fb0e67SStephan Aßmus fShadowTextView->MakeEditable(false); 3225fb0e67SStephan Aßmus fShadowTextView->SetWordWrap(false); 3325fb0e67SStephan Aßmus fShadowTextView->SetAlignment(B_ALIGN_CENTER); 3425fb0e67SStephan Aßmus } 3525fb0e67SStephan Aßmus 3625fb0e67SStephan Aßmus 3725fb0e67SStephan Aßmus SubtitleBitmap::~SubtitleBitmap() 3825fb0e67SStephan Aßmus { 3925fb0e67SStephan Aßmus delete fBitmap; 4025fb0e67SStephan Aßmus delete fTextView; 4125fb0e67SStephan Aßmus delete fShadowTextView; 4225fb0e67SStephan Aßmus } 4325fb0e67SStephan Aßmus 4425fb0e67SStephan Aßmus 4525fb0e67SStephan Aßmus void 4625fb0e67SStephan Aßmus SubtitleBitmap::SetText(const char* text) 4725fb0e67SStephan Aßmus { 4825fb0e67SStephan Aßmus if (text == fText) 4925fb0e67SStephan Aßmus return; 5025fb0e67SStephan Aßmus 5125fb0e67SStephan Aßmus fText = text; 5225fb0e67SStephan Aßmus 5325fb0e67SStephan Aßmus _GenerateBitmap(); 5425fb0e67SStephan Aßmus } 5525fb0e67SStephan Aßmus 5625fb0e67SStephan Aßmus 5725fb0e67SStephan Aßmus void 5825fb0e67SStephan Aßmus SubtitleBitmap::SetVideoBounds(BRect bounds) 5925fb0e67SStephan Aßmus { 6025fb0e67SStephan Aßmus if (bounds == fVideoBounds) 6125fb0e67SStephan Aßmus return; 6225fb0e67SStephan Aßmus 6325fb0e67SStephan Aßmus fVideoBounds = bounds; 6425fb0e67SStephan Aßmus 65*01e0d327SStephan Aßmus fUseSoftShadow = true; 66*01e0d327SStephan Aßmus _GenerateBitmap(); 67*01e0d327SStephan Aßmus } 68*01e0d327SStephan Aßmus 69*01e0d327SStephan Aßmus 70*01e0d327SStephan Aßmus void 71*01e0d327SStephan Aßmus SubtitleBitmap::SetOverlayMode(bool overlayMode) 72*01e0d327SStephan Aßmus { 73*01e0d327SStephan Aßmus if (overlayMode == fOverlayMode) 74*01e0d327SStephan Aßmus return; 75*01e0d327SStephan Aßmus 76*01e0d327SStephan Aßmus fOverlayMode = overlayMode; 77*01e0d327SStephan Aßmus 7825fb0e67SStephan Aßmus _GenerateBitmap(); 7925fb0e67SStephan Aßmus } 8025fb0e67SStephan Aßmus 8125fb0e67SStephan Aßmus 8225fb0e67SStephan Aßmus const BBitmap* 8325fb0e67SStephan Aßmus SubtitleBitmap::Bitmap() const 8425fb0e67SStephan Aßmus { 8525fb0e67SStephan Aßmus return fBitmap; 8625fb0e67SStephan Aßmus } 8725fb0e67SStephan Aßmus 8825fb0e67SStephan Aßmus 8925fb0e67SStephan Aßmus void 9025fb0e67SStephan Aßmus SubtitleBitmap::_GenerateBitmap() 9125fb0e67SStephan Aßmus { 9225fb0e67SStephan Aßmus if (!fVideoBounds.IsValid()) 9325fb0e67SStephan Aßmus return; 9425fb0e67SStephan Aßmus 9525fb0e67SStephan Aßmus delete fBitmap; 9625fb0e67SStephan Aßmus 97c8ccdf52SStephan Aßmus BRect bounds; 98c8ccdf52SStephan Aßmus float outlineRadius; 99*01e0d327SStephan Aßmus _InsertText(bounds, outlineRadius, fOverlayMode); 100*01e0d327SStephan Aßmus 101*01e0d327SStephan Aßmus bigtime_t startTime = 0; 102*01e0d327SStephan Aßmus if (!fOverlayMode && fUseSoftShadow) 103*01e0d327SStephan Aßmus startTime = system_time(); 10425fb0e67SStephan Aßmus 10525fb0e67SStephan Aßmus fBitmap = new BBitmap(bounds, B_BITMAP_ACCEPTS_VIEWS, B_RGBA32); 10625fb0e67SStephan Aßmus memset(fBitmap->Bits(), 0, fBitmap->BitsLength()); 10725fb0e67SStephan Aßmus 10825fb0e67SStephan Aßmus if (fBitmap->Lock()) { 10925fb0e67SStephan Aßmus fBitmap->AddChild(fShadowTextView); 11025fb0e67SStephan Aßmus fShadowTextView->ResizeTo(bounds.Width(), bounds.Height()); 11125fb0e67SStephan Aßmus 11225fb0e67SStephan Aßmus fShadowTextView->SetViewColor(0, 0, 0, 0); 11325fb0e67SStephan Aßmus fShadowTextView->SetDrawingMode(B_OP_ALPHA); 11425fb0e67SStephan Aßmus fShadowTextView->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_COMPOSITE); 11525fb0e67SStephan Aßmus 11625fb0e67SStephan Aßmus fShadowTextView->PushState(); 11725fb0e67SStephan Aßmus fShadowTextView->Draw(bounds); 11825fb0e67SStephan Aßmus fShadowTextView->PopState(); 11925fb0e67SStephan Aßmus 120*01e0d327SStephan Aßmus if (!fOverlayMode && fUseSoftShadow) { 12125fb0e67SStephan Aßmus fShadowTextView->Sync(); 122*01e0d327SStephan Aßmus StackBlurFilter filter; 123c8ccdf52SStephan Aßmus filter.Filter(fBitmap, outlineRadius * 2); 124*01e0d327SStephan Aßmus } 125*01e0d327SStephan Aßmus 126*01e0d327SStephan Aßmus fShadowTextView->RemoveSelf(); 127c8ccdf52SStephan Aßmus 12825fb0e67SStephan Aßmus fBitmap->AddChild(fTextView); 12925fb0e67SStephan Aßmus fTextView->ResizeTo(bounds.Width(), bounds.Height()); 130*01e0d327SStephan Aßmus if (!fOverlayMode && fUseSoftShadow) 131c8ccdf52SStephan Aßmus fTextView->MoveTo(-outlineRadius / 2, -outlineRadius / 2); 132*01e0d327SStephan Aßmus else 133*01e0d327SStephan Aßmus fTextView->MoveTo(0, 0); 13425fb0e67SStephan Aßmus 13525fb0e67SStephan Aßmus fTextView->SetViewColor(0, 0, 0, 0); 13625fb0e67SStephan Aßmus fTextView->SetDrawingMode(B_OP_ALPHA); 13725fb0e67SStephan Aßmus fTextView->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_COMPOSITE); 13825fb0e67SStephan Aßmus 13925fb0e67SStephan Aßmus fTextView->PushState(); 14025fb0e67SStephan Aßmus fTextView->Draw(bounds); 14125fb0e67SStephan Aßmus fTextView->PopState(); 14225fb0e67SStephan Aßmus 14325fb0e67SStephan Aßmus fTextView->Sync(); 14425fb0e67SStephan Aßmus fTextView->RemoveSelf(); 14525fb0e67SStephan Aßmus 14625fb0e67SStephan Aßmus fBitmap->Unlock(); 14725fb0e67SStephan Aßmus } 148*01e0d327SStephan Aßmus 149*01e0d327SStephan Aßmus if (!fOverlayMode && fUseSoftShadow && system_time() - startTime > 10000) 150*01e0d327SStephan Aßmus fUseSoftShadow = false; 15125fb0e67SStephan Aßmus } 15225fb0e67SStephan Aßmus 15325fb0e67SStephan Aßmus 15425fb0e67SStephan Aßmus struct ParseState { 1554ad39ed7SStephan Aßmus ParseState(rgb_color color) 15625fb0e67SStephan Aßmus : 1574ad39ed7SStephan Aßmus color(color), 15825fb0e67SStephan Aßmus bold(false), 15925fb0e67SStephan Aßmus italic(false), 16025fb0e67SStephan Aßmus underlined(false), 16125fb0e67SStephan Aßmus 16225fb0e67SStephan Aßmus previous(NULL) 16325fb0e67SStephan Aßmus { 16425fb0e67SStephan Aßmus } 16525fb0e67SStephan Aßmus 16625fb0e67SStephan Aßmus ParseState(ParseState* previous) 16725fb0e67SStephan Aßmus : 16825fb0e67SStephan Aßmus color(previous->color), 16925fb0e67SStephan Aßmus bold(previous->bold), 17025fb0e67SStephan Aßmus italic(previous->italic), 17125fb0e67SStephan Aßmus underlined(previous->underlined), 17225fb0e67SStephan Aßmus 17325fb0e67SStephan Aßmus previous(previous) 17425fb0e67SStephan Aßmus { 17525fb0e67SStephan Aßmus } 1764ad39ed7SStephan Aßmus 17725fb0e67SStephan Aßmus rgb_color color; 17825fb0e67SStephan Aßmus bool bold; 17925fb0e67SStephan Aßmus bool italic; 18025fb0e67SStephan Aßmus bool underlined; 18125fb0e67SStephan Aßmus 18225fb0e67SStephan Aßmus ParseState* previous; 18325fb0e67SStephan Aßmus }; 18425fb0e67SStephan Aßmus 18525fb0e67SStephan Aßmus 18625fb0e67SStephan Aßmus static bool 18725fb0e67SStephan Aßmus find_next_tag(const BString& string, int32& tagPos, int32& tagLength, 18825fb0e67SStephan Aßmus ParseState*& state) 18925fb0e67SStephan Aßmus { 19025fb0e67SStephan Aßmus static const char* kTags[] = { 19125fb0e67SStephan Aßmus "<b>", "</b>", 19225fb0e67SStephan Aßmus "<i>", "</i>", 19325fb0e67SStephan Aßmus "<u>", "</u>", 19425fb0e67SStephan Aßmus "<font color=\"#", "</font>" 19525fb0e67SStephan Aßmus }; 19625fb0e67SStephan Aßmus static const int32 kTagCount = sizeof(kTags) / sizeof(const char*); 19725fb0e67SStephan Aßmus 1984ad39ed7SStephan Aßmus int32 startPos = tagPos; 19925fb0e67SStephan Aßmus tagPos = string.Length(); 20025fb0e67SStephan Aßmus tagLength = 0; 20125fb0e67SStephan Aßmus 202ed74106bSStephan Aßmus // Find the next tag closest from the current position 203ed74106bSStephan Aßmus // This way of doing it allows broken input with overlapping tags even. 20425fb0e67SStephan Aßmus BString tag; 20525fb0e67SStephan Aßmus for (int32 i = 0; i < kTagCount; i++) { 2064ad39ed7SStephan Aßmus int32 nextTag = string.IFindFirst(kTags[i], startPos); 2074ad39ed7SStephan Aßmus if (nextTag >= startPos && nextTag < tagPos) { 20825fb0e67SStephan Aßmus tagPos = nextTag; 20925fb0e67SStephan Aßmus tag = kTags[i]; 21025fb0e67SStephan Aßmus } 21125fb0e67SStephan Aßmus } 21225fb0e67SStephan Aßmus 2134ad39ed7SStephan Aßmus if (tag.Length() == 0) 2144ad39ed7SStephan Aßmus return false; 2154ad39ed7SStephan Aßmus 216ed74106bSStephan Aßmus // Tag found, ParseState will change. 21725fb0e67SStephan Aßmus tagLength = tag.Length(); 21825fb0e67SStephan Aßmus if (tag == "<b>") { 21925fb0e67SStephan Aßmus state = new ParseState(state); 22025fb0e67SStephan Aßmus state->bold = true; 22125fb0e67SStephan Aßmus } else if (tag == "<i>") { 22225fb0e67SStephan Aßmus state = new ParseState(state); 22325fb0e67SStephan Aßmus state->italic = true; 22425fb0e67SStephan Aßmus } else if (tag == "<u>") { 22525fb0e67SStephan Aßmus state = new ParseState(state); 22625fb0e67SStephan Aßmus state->underlined = true; 22725fb0e67SStephan Aßmus } else if (tag == "<font color=\"#") { 22825fb0e67SStephan Aßmus state = new ParseState(state); 22925fb0e67SStephan Aßmus char number[16]; 23025fb0e67SStephan Aßmus snprintf(number, sizeof(number), "0x%.6s", 23125fb0e67SStephan Aßmus string.String() + tagPos + tag.Length()); 23225fb0e67SStephan Aßmus int colorInt; 23325fb0e67SStephan Aßmus if (sscanf(number, "%x", &colorInt) == 1) { 23425fb0e67SStephan Aßmus state->color.red = (colorInt & 0xff0000) >> 16; 23525fb0e67SStephan Aßmus state->color.green = (colorInt & 0x00ff00) >> 8; 23625fb0e67SStephan Aßmus state->color.blue = (colorInt & 0x0000ff); 2374ad39ed7SStephan Aßmus // skip 'RRGGBB">' part, too 23825fb0e67SStephan Aßmus tagLength += 8; 23925fb0e67SStephan Aßmus } 24025fb0e67SStephan Aßmus } else if (tag == "</b>" || tag == "</i>" || tag == "</u>" 24125fb0e67SStephan Aßmus || tag == "</font>") { 242ed74106bSStephan Aßmus // Closing tag, pop state 24325fb0e67SStephan Aßmus if (state->previous != NULL) { 24425fb0e67SStephan Aßmus ParseState* oldState = state; 24525fb0e67SStephan Aßmus state = state->previous; 24625fb0e67SStephan Aßmus delete oldState; 24725fb0e67SStephan Aßmus } 24825fb0e67SStephan Aßmus } 24925fb0e67SStephan Aßmus return true; 25025fb0e67SStephan Aßmus } 25125fb0e67SStephan Aßmus 25225fb0e67SStephan Aßmus 25325fb0e67SStephan Aßmus static void 25425fb0e67SStephan Aßmus apply_state(BTextView* textView, const ParseState* state, BFont font, 25525fb0e67SStephan Aßmus bool changeColor) 25625fb0e67SStephan Aßmus { 25725fb0e67SStephan Aßmus uint16 face = 0; 25825fb0e67SStephan Aßmus if (state->bold || state->italic || state->underlined) { 25925fb0e67SStephan Aßmus if (state->bold) 26025fb0e67SStephan Aßmus face |= B_BOLD_FACE; 26125fb0e67SStephan Aßmus if (state->italic) 26225fb0e67SStephan Aßmus face |= B_ITALIC_FACE; 2634ad39ed7SStephan Aßmus // NOTE: This is probably not supported by the app_server (perhaps 2644ad39ed7SStephan Aßmus // it is if the font contains a specific underline face). 26525fb0e67SStephan Aßmus if (state->underlined) 26625fb0e67SStephan Aßmus face |= B_UNDERSCORE_FACE; 26725fb0e67SStephan Aßmus } else 26825fb0e67SStephan Aßmus face = B_REGULAR_FACE; 26925fb0e67SStephan Aßmus font.SetFace(face); 27025fb0e67SStephan Aßmus if (changeColor) 27125fb0e67SStephan Aßmus textView->SetFontAndColor(&font, B_FONT_ALL, &state->color); 27225fb0e67SStephan Aßmus else 27325fb0e67SStephan Aßmus textView->SetFontAndColor(&font, B_FONT_ALL, NULL); 27425fb0e67SStephan Aßmus } 27525fb0e67SStephan Aßmus 27625fb0e67SStephan Aßmus 27725fb0e67SStephan Aßmus static void 27825fb0e67SStephan Aßmus parse_text(const BString& string, BTextView* textView, const BFont& font, 2794ad39ed7SStephan Aßmus const rgb_color& color, bool changeColor) 28025fb0e67SStephan Aßmus { 2814ad39ed7SStephan Aßmus ParseState rootState(color); 2824ad39ed7SStephan Aßmus // Colors may change, but alpha channel will be preserved 2834ad39ed7SStephan Aßmus 28425fb0e67SStephan Aßmus ParseState* state = &rootState; 28525fb0e67SStephan Aßmus 28625fb0e67SStephan Aßmus int32 pos = 0; 28725fb0e67SStephan Aßmus while (pos < string.Length()) { 28825fb0e67SStephan Aßmus int32 nextPos = pos; 28925fb0e67SStephan Aßmus int32 tagLength; 29025fb0e67SStephan Aßmus bool stateChanged = find_next_tag(string, nextPos, tagLength, state); 29125fb0e67SStephan Aßmus if (nextPos > pos) { 292ed74106bSStephan Aßmus // Insert text between last and next tags 29325fb0e67SStephan Aßmus BString subString; 29425fb0e67SStephan Aßmus string.CopyInto(subString, pos, nextPos - pos); 29525fb0e67SStephan Aßmus textView->Insert(subString.String()); 29625fb0e67SStephan Aßmus } 29725fb0e67SStephan Aßmus pos = nextPos + tagLength; 29825fb0e67SStephan Aßmus if (stateChanged) 29925fb0e67SStephan Aßmus apply_state(textView, state, font, changeColor); 30025fb0e67SStephan Aßmus } 301ed74106bSStephan Aßmus 302ed74106bSStephan Aßmus // Cleanup states in case the input text had non-matching tags. 303ed74106bSStephan Aßmus while (state->previous != NULL) { 304ed74106bSStephan Aßmus ParseState* oldState = state->previous; 305ed74106bSStephan Aßmus state = state->previous; 306ed74106bSStephan Aßmus delete oldState; 307ed74106bSStephan Aßmus } 30825fb0e67SStephan Aßmus } 30925fb0e67SStephan Aßmus 31025fb0e67SStephan Aßmus 311c8ccdf52SStephan Aßmus void 312*01e0d327SStephan Aßmus SubtitleBitmap::_InsertText(BRect& textRect, float& outlineRadius, 313*01e0d327SStephan Aßmus bool overlayMode) 31425fb0e67SStephan Aßmus { 31525fb0e67SStephan Aßmus BFont font(be_plain_font); 316c8ccdf52SStephan Aßmus float fontSize = ceilf((fVideoBounds.Width() * 0.9) / 36); 317c8ccdf52SStephan Aßmus outlineRadius = ceilf(fontSize / 28.0); 31825fb0e67SStephan Aßmus font.SetSize(fontSize); 31925fb0e67SStephan Aßmus 32025fb0e67SStephan Aßmus rgb_color shadow; 32125fb0e67SStephan Aßmus shadow.red = 0; 32225fb0e67SStephan Aßmus shadow.green = 0; 32325fb0e67SStephan Aßmus shadow.blue = 0; 32425fb0e67SStephan Aßmus shadow.alpha = 200; 32525fb0e67SStephan Aßmus 32625fb0e67SStephan Aßmus rgb_color color; 32725fb0e67SStephan Aßmus color.red = 255; 32825fb0e67SStephan Aßmus color.green = 255; 32925fb0e67SStephan Aßmus color.blue = 255; 33025fb0e67SStephan Aßmus color.alpha = 240; 33125fb0e67SStephan Aßmus 332c8ccdf52SStephan Aßmus textRect = fVideoBounds; 333c8ccdf52SStephan Aßmus textRect.OffsetBy(outlineRadius, outlineRadius); 33425fb0e67SStephan Aßmus 33525fb0e67SStephan Aßmus fTextView->SetText(NULL); 33625fb0e67SStephan Aßmus fTextView->SetFontAndColor(&font, B_FONT_ALL, &color); 33725fb0e67SStephan Aßmus 33825fb0e67SStephan Aßmus fTextView->Insert(" "); 3394ad39ed7SStephan Aßmus parse_text(fText, fTextView, font, color, true); 34025fb0e67SStephan Aßmus 341c8ccdf52SStephan Aßmus font.SetFalseBoldWidth(outlineRadius); 342*01e0d327SStephan Aßmus fShadowTextView->ForceFontAliasing(overlayMode); 34325fb0e67SStephan Aßmus fShadowTextView->SetText(NULL); 34425fb0e67SStephan Aßmus fShadowTextView->SetFontAndColor(&font, B_FONT_ALL, &shadow); 34525fb0e67SStephan Aßmus 34625fb0e67SStephan Aßmus fShadowTextView->Insert(" "); 3474ad39ed7SStephan Aßmus parse_text(fText, fShadowTextView, font, shadow, false); 34825fb0e67SStephan Aßmus 34925fb0e67SStephan Aßmus // This causes the BTextView to calculate the layout of the text 35025fb0e67SStephan Aßmus fTextView->SetTextRect(BRect(0, 0, 0, 0)); 35125fb0e67SStephan Aßmus fTextView->SetTextRect(textRect); 35225fb0e67SStephan Aßmus fShadowTextView->SetTextRect(BRect(0, 0, 0, 0)); 35325fb0e67SStephan Aßmus fShadowTextView->SetTextRect(textRect); 35425fb0e67SStephan Aßmus 35525fb0e67SStephan Aßmus textRect = fTextView->TextRect(); 356c8ccdf52SStephan Aßmus textRect.InsetBy(-outlineRadius, -outlineRadius); 35725fb0e67SStephan Aßmus textRect.OffsetTo(B_ORIGIN); 358c8ccdf52SStephan Aßmus 359c8ccdf52SStephan Aßmus // Make sure the text rect really finishes behind the last line. 360c8ccdf52SStephan Aßmus // We don't want any accidental extra space. 361c8ccdf52SStephan Aßmus textRect.bottom = outlineRadius; 362c8ccdf52SStephan Aßmus int32 lineCount = fTextView->CountLines(); 363c8ccdf52SStephan Aßmus for (int32 i = 0; i < lineCount; i++) 364c8ccdf52SStephan Aßmus textRect.bottom += fTextView->LineHeight(i); 365c8ccdf52SStephan Aßmus textRect.bottom += outlineRadius; 36625fb0e67SStephan Aßmus } 36725fb0e67SStephan Aßmus 36825fb0e67SStephan Aßmus 36925fb0e67SStephan Aßmus 370