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