1 /* 2 * Copyright 2010, Stephan Aßmus <superstippi@gmx.de>. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7 #include "SubtitleBitmap.h" 8 9 #include <stdio.h> 10 11 #include <Bitmap.h> 12 #include <TextView.h> 13 14 #include "StackBlurFilter.h" 15 16 17 SubtitleBitmap::SubtitleBitmap() 18 : 19 fBitmap(NULL), 20 fTextView(new BTextView("offscreen text")), 21 fShadowTextView(new BTextView("offscreen text shadow")), 22 fCharsPerLine(36), 23 fUseSoftShadow(true), 24 fOverlayMode(false) 25 { 26 fTextView->SetStylable(true); 27 fTextView->MakeEditable(false); 28 fTextView->SetWordWrap(false); 29 fTextView->SetAlignment(B_ALIGN_CENTER); 30 31 fShadowTextView->SetStylable(true); 32 fShadowTextView->MakeEditable(false); 33 fShadowTextView->SetWordWrap(false); 34 fShadowTextView->SetAlignment(B_ALIGN_CENTER); 35 } 36 37 38 SubtitleBitmap::~SubtitleBitmap() 39 { 40 delete fBitmap; 41 delete fTextView; 42 delete fShadowTextView; 43 } 44 45 46 bool 47 SubtitleBitmap::SetText(const char* text) 48 { 49 if (text == fText) 50 return false; 51 52 fText = text; 53 54 _GenerateBitmap(); 55 return true; 56 } 57 58 59 void 60 SubtitleBitmap::SetVideoBounds(BRect bounds) 61 { 62 if (bounds == fVideoBounds) 63 return; 64 65 fVideoBounds = bounds; 66 67 fUseSoftShadow = true; 68 _GenerateBitmap(); 69 } 70 71 72 void 73 SubtitleBitmap::SetOverlayMode(bool overlayMode) 74 { 75 if (overlayMode == fOverlayMode) 76 return; 77 78 fOverlayMode = overlayMode; 79 80 _GenerateBitmap(); 81 } 82 83 84 void 85 SubtitleBitmap::SetCharsPerLine(float charsPerLine) 86 { 87 if (charsPerLine == fCharsPerLine) 88 return; 89 90 fCharsPerLine = charsPerLine; 91 92 fUseSoftShadow = true; 93 _GenerateBitmap(); 94 } 95 96 97 const BBitmap* 98 SubtitleBitmap::Bitmap() const 99 { 100 return fBitmap; 101 } 102 103 104 void 105 SubtitleBitmap::_GenerateBitmap() 106 { 107 if (!fVideoBounds.IsValid()) 108 return; 109 110 delete fBitmap; 111 112 BRect bounds; 113 float outlineRadius; 114 _InsertText(bounds, outlineRadius, fOverlayMode); 115 116 bigtime_t startTime = 0; 117 if (!fOverlayMode && fUseSoftShadow) 118 startTime = system_time(); 119 120 fBitmap = new BBitmap(bounds, B_BITMAP_ACCEPTS_VIEWS, B_RGBA32); 121 memset(fBitmap->Bits(), 0, fBitmap->BitsLength()); 122 123 if (fBitmap->Lock()) { 124 fBitmap->AddChild(fShadowTextView); 125 fShadowTextView->ResizeTo(bounds.Width(), bounds.Height()); 126 127 fShadowTextView->SetViewColor(0, 0, 0, 0); 128 fShadowTextView->SetDrawingMode(B_OP_ALPHA); 129 fShadowTextView->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_COMPOSITE); 130 131 fShadowTextView->PushState(); 132 fShadowTextView->Draw(bounds); 133 fShadowTextView->PopState(); 134 135 if (!fOverlayMode && fUseSoftShadow) { 136 fShadowTextView->Sync(); 137 StackBlurFilter filter; 138 filter.Filter(fBitmap, outlineRadius * 2); 139 } 140 141 fShadowTextView->RemoveSelf(); 142 143 fBitmap->AddChild(fTextView); 144 fTextView->ResizeTo(bounds.Width(), bounds.Height()); 145 if (!fOverlayMode && fUseSoftShadow) 146 fTextView->MoveTo(-outlineRadius / 2, -outlineRadius / 2); 147 else 148 fTextView->MoveTo(0, 0); 149 150 fTextView->SetViewColor(0, 0, 0, 0); 151 fTextView->SetDrawingMode(B_OP_ALPHA); 152 fTextView->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_COMPOSITE); 153 154 fTextView->PushState(); 155 fTextView->Draw(bounds); 156 fTextView->PopState(); 157 158 fTextView->Sync(); 159 fTextView->RemoveSelf(); 160 161 fBitmap->Unlock(); 162 } 163 164 if (!fOverlayMode && fUseSoftShadow && system_time() - startTime > 10000) 165 fUseSoftShadow = false; 166 } 167 168 169 struct ParseState { 170 ParseState(rgb_color color) 171 : 172 color(color), 173 bold(false), 174 italic(false), 175 underlined(false), 176 177 previous(NULL) 178 { 179 } 180 181 ParseState(ParseState* previous) 182 : 183 color(previous->color), 184 bold(previous->bold), 185 italic(previous->italic), 186 underlined(previous->underlined), 187 188 previous(previous) 189 { 190 } 191 192 rgb_color color; 193 bool bold; 194 bool italic; 195 bool underlined; 196 197 ParseState* previous; 198 }; 199 200 201 static bool 202 find_next_tag(const BString& string, int32& tagPos, int32& tagLength, 203 ParseState*& state) 204 { 205 static const char* kTags[] = { 206 "<b>", "</b>", 207 "<i>", "</i>", 208 "<u>", "</u>", 209 "<font color=\"#", "</font>" 210 }; 211 static const int32 kTagCount = sizeof(kTags) / sizeof(const char*); 212 213 int32 startPos = tagPos; 214 tagPos = string.Length(); 215 tagLength = 0; 216 217 // Find the next tag closest from the current position 218 // This way of doing it allows broken input with overlapping tags even. 219 BString tag; 220 for (int32 i = 0; i < kTagCount; i++) { 221 int32 nextTag = string.IFindFirst(kTags[i], startPos); 222 if (nextTag >= startPos && nextTag < tagPos) { 223 tagPos = nextTag; 224 tag = kTags[i]; 225 } 226 } 227 228 if (tag.Length() == 0) 229 return false; 230 231 // Tag found, ParseState will change. 232 tagLength = tag.Length(); 233 if (tag == "<b>") { 234 state = new ParseState(state); 235 state->bold = true; 236 } else if (tag == "<i>") { 237 state = new ParseState(state); 238 state->italic = true; 239 } else if (tag == "<u>") { 240 state = new ParseState(state); 241 state->underlined = true; 242 } else if (tag == "<font color=\"#") { 243 state = new ParseState(state); 244 char number[16]; 245 snprintf(number, sizeof(number), "0x%.6s", 246 string.String() + tagPos + tag.Length()); 247 int colorInt; 248 if (sscanf(number, "%x", &colorInt) == 1) { 249 state->color.red = (colorInt & 0xff0000) >> 16; 250 state->color.green = (colorInt & 0x00ff00) >> 8; 251 state->color.blue = (colorInt & 0x0000ff); 252 // skip 'RRGGBB">' part, too 253 tagLength += 8; 254 } 255 } else if (tag == "</b>" || tag == "</i>" || tag == "</u>" 256 || tag == "</font>") { 257 // Closing tag, pop state 258 if (state->previous != NULL) { 259 ParseState* oldState = state; 260 state = state->previous; 261 delete oldState; 262 } 263 } 264 return true; 265 } 266 267 268 static void 269 apply_state(BTextView* textView, const ParseState* state, BFont font, 270 bool changeColor) 271 { 272 uint16 face = 0; 273 if (state->bold || state->italic || state->underlined) { 274 if (state->bold) 275 face |= B_BOLD_FACE; 276 if (state->italic) 277 face |= B_ITALIC_FACE; 278 // NOTE: This is probably not supported by the app_server (perhaps 279 // it is if the font contains a specific underline face). 280 if (state->underlined) 281 face |= B_UNDERSCORE_FACE; 282 } else 283 face = B_REGULAR_FACE; 284 font.SetFace(face); 285 if (changeColor) 286 textView->SetFontAndColor(&font, B_FONT_ALL, &state->color); 287 else 288 textView->SetFontAndColor(&font, B_FONT_ALL, NULL); 289 } 290 291 292 static void 293 parse_text(const BString& string, BTextView* textView, const BFont& font, 294 const rgb_color& color, bool changeColor) 295 { 296 ParseState rootState(color); 297 // Colors may change, but alpha channel will be preserved 298 299 ParseState* state = &rootState; 300 301 int32 pos = 0; 302 while (pos < string.Length()) { 303 int32 nextPos = pos; 304 int32 tagLength; 305 bool stateChanged = find_next_tag(string, nextPos, tagLength, state); 306 if (nextPos > pos) { 307 // Insert text between last and next tags 308 BString subString; 309 string.CopyInto(subString, pos, nextPos - pos); 310 textView->Insert(subString.String()); 311 } 312 pos = nextPos + tagLength; 313 if (stateChanged) 314 apply_state(textView, state, font, changeColor); 315 } 316 317 // Cleanup states in case the input text had non-matching tags. 318 while (state->previous != NULL) { 319 ParseState* oldState = state; 320 state = state->previous; 321 delete oldState; 322 } 323 } 324 325 326 void 327 SubtitleBitmap::_InsertText(BRect& textRect, float& outlineRadius, 328 bool overlayMode) 329 { 330 BFont font(be_plain_font); 331 float fontSize = ceilf((fVideoBounds.Width() * 0.9) / fCharsPerLine); 332 outlineRadius = ceilf(fontSize / 28.0); 333 font.SetSize(fontSize); 334 335 rgb_color shadow; 336 shadow.red = 0; 337 shadow.green = 0; 338 shadow.blue = 0; 339 shadow.alpha = 200; 340 341 rgb_color color; 342 color.red = 255; 343 color.green = 255; 344 color.blue = 255; 345 color.alpha = 240; 346 347 textRect = fVideoBounds; 348 textRect.OffsetBy(outlineRadius, outlineRadius); 349 350 fTextView->SetText(NULL); 351 fTextView->SetFontAndColor(&font, B_FONT_ALL, &color); 352 353 fTextView->Insert(" "); 354 parse_text(fText, fTextView, font, color, true); 355 356 font.SetFalseBoldWidth(outlineRadius); 357 fShadowTextView->ForceFontAliasing(overlayMode); 358 fShadowTextView->SetText(NULL); 359 fShadowTextView->SetFontAndColor(&font, B_FONT_ALL, &shadow); 360 361 fShadowTextView->Insert(" "); 362 parse_text(fText, fShadowTextView, font, shadow, false); 363 364 // This causes the BTextView to calculate the layout of the text 365 fTextView->SetTextRect(BRect(0, 0, 0, 0)); 366 fTextView->SetTextRect(textRect); 367 fShadowTextView->SetTextRect(BRect(0, 0, 0, 0)); 368 fShadowTextView->SetTextRect(textRect); 369 370 textRect = fTextView->TextRect(); 371 textRect.InsetBy(-outlineRadius, -outlineRadius); 372 textRect.OffsetTo(B_ORIGIN); 373 374 // Make sure the text rect really finishes behind the last line. 375 // We don't want any accidental extra space. 376 textRect.bottom = outlineRadius; 377 int32 lineCount = fTextView->CountLines(); 378 for (int32 i = 0; i < lineCount; i++) 379 textRect.bottom += fTextView->LineHeight(i); 380 textRect.bottom += outlineRadius; 381 } 382 383 384 385