2 * Copyright 2010, Stephan Aßmus <superstippi@gmx.de>.
3 * Distributed under the terms of the MIT License.
7 #include "SubtitleBitmap.h"
14 #include "StackBlurFilter.h"
17 SubtitleBitmap::SubtitleBitmap()
20 fTextView(new BTextView("offscreen text")),
21 fShadowTextView(new BTextView("offscreen text shadow")),
26 fTextView
->SetStylable(true);
27 fTextView
->MakeEditable(false);
28 fTextView
->SetWordWrap(false);
29 fTextView
->SetAlignment(B_ALIGN_CENTER
);
31 fShadowTextView
->SetStylable(true);
32 fShadowTextView
->MakeEditable(false);
33 fShadowTextView
->SetWordWrap(false);
34 fShadowTextView
->SetAlignment(B_ALIGN_CENTER
);
38 SubtitleBitmap::~SubtitleBitmap()
42 delete fShadowTextView
;
47 SubtitleBitmap::SetText(const char* text
)
60 SubtitleBitmap::SetVideoBounds(BRect bounds
)
62 if (bounds
== fVideoBounds
)
65 fVideoBounds
= bounds
;
67 fUseSoftShadow
= true;
73 SubtitleBitmap::SetOverlayMode(bool overlayMode
)
75 if (overlayMode
== fOverlayMode
)
78 fOverlayMode
= overlayMode
;
85 SubtitleBitmap::SetCharsPerLine(float charsPerLine
)
87 if (charsPerLine
== fCharsPerLine
)
90 fCharsPerLine
= charsPerLine
;
92 fUseSoftShadow
= true;
98 SubtitleBitmap::Bitmap() const
105 SubtitleBitmap::_GenerateBitmap()
107 if (!fVideoBounds
.IsValid())
114 _InsertText(bounds
, outlineRadius
, fOverlayMode
);
116 bigtime_t startTime
= 0;
117 if (!fOverlayMode
&& fUseSoftShadow
)
118 startTime
= system_time();
120 fBitmap
= new BBitmap(bounds
, B_BITMAP_ACCEPTS_VIEWS
, B_RGBA32
);
121 memset(fBitmap
->Bits(), 0, fBitmap
->BitsLength());
123 if (fBitmap
->Lock()) {
124 fBitmap
->AddChild(fShadowTextView
);
125 fShadowTextView
->ResizeTo(bounds
.Width(), bounds
.Height());
127 fShadowTextView
->SetViewColor(0, 0, 0, 0);
128 fShadowTextView
->SetDrawingMode(B_OP_ALPHA
);
129 fShadowTextView
->SetBlendingMode(B_PIXEL_ALPHA
, B_ALPHA_COMPOSITE
);
131 fShadowTextView
->PushState();
132 fShadowTextView
->Draw(bounds
);
133 fShadowTextView
->PopState();
135 if (!fOverlayMode
&& fUseSoftShadow
) {
136 fShadowTextView
->Sync();
137 StackBlurFilter filter
;
138 filter
.Filter(fBitmap
, outlineRadius
* 2);
141 fShadowTextView
->RemoveSelf();
143 fBitmap
->AddChild(fTextView
);
144 fTextView
->ResizeTo(bounds
.Width(), bounds
.Height());
145 if (!fOverlayMode
&& fUseSoftShadow
)
146 fTextView
->MoveTo(-outlineRadius
/ 2, -outlineRadius
/ 2);
148 fTextView
->MoveTo(0, 0);
150 fTextView
->SetViewColor(0, 0, 0, 0);
151 fTextView
->SetDrawingMode(B_OP_ALPHA
);
152 fTextView
->SetBlendingMode(B_PIXEL_ALPHA
, B_ALPHA_COMPOSITE
);
154 fTextView
->PushState();
155 fTextView
->Draw(bounds
);
156 fTextView
->PopState();
159 fTextView
->RemoveSelf();
164 if (!fOverlayMode
&& fUseSoftShadow
&& system_time() - startTime
> 10000)
165 fUseSoftShadow
= false;
170 ParseState(rgb_color color
)
181 ParseState(ParseState
* previous
)
183 color(previous
->color
),
184 bold(previous
->bold
),
185 italic(previous
->italic
),
186 underlined(previous
->underlined
),
197 ParseState
* previous
;
202 find_next_tag(const BString
& string
, int32
& tagPos
, int32
& tagLength
,
205 static const char* kTags
[] = {
209 "<font color=\"#", "</font>"
211 static const int32 kTagCount
= sizeof(kTags
) / sizeof(const char*);
213 int32 startPos
= tagPos
;
214 tagPos
= string
.Length();
217 // Find the next tag closest from the current position
218 // This way of doing it allows broken input with overlapping tags even.
220 for (int32 i
= 0; i
< kTagCount
; i
++) {
221 int32 nextTag
= string
.IFindFirst(kTags
[i
], startPos
);
222 if (nextTag
>= startPos
&& nextTag
< tagPos
) {
228 if (tag
.Length() == 0)
231 // Tag found, ParseState will change.
232 tagLength
= tag
.Length();
234 state
= new ParseState(state
);
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
);
245 snprintf(number
, sizeof(number
), "0x%.6s",
246 string
.String() + tagPos
+ tag
.Length());
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
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
;
269 apply_state(BTextView
* textView
, const ParseState
* state
, BFont font
,
273 if (state
->bold
|| state
->italic
|| state
->underlined
) {
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
;
283 face
= B_REGULAR_FACE
;
286 textView
->SetFontAndColor(&font
, B_FONT_ALL
, &state
->color
);
288 textView
->SetFontAndColor(&font
, B_FONT_ALL
, NULL
);
293 parse_text(const BString
& string
, BTextView
* textView
, const BFont
& font
,
294 const rgb_color
& color
, bool changeColor
)
296 ParseState
rootState(color
);
297 // Colors may change, but alpha channel will be preserved
299 ParseState
* state
= &rootState
;
302 while (pos
< string
.Length()) {
305 bool stateChanged
= find_next_tag(string
, nextPos
, tagLength
, state
);
307 // Insert text between last and next tags
309 string
.CopyInto(subString
, pos
, nextPos
- pos
);
310 textView
->Insert(subString
.String());
312 pos
= nextPos
+ tagLength
;
314 apply_state(textView
, state
, font
, changeColor
);
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
;
327 SubtitleBitmap::_InsertText(BRect
& textRect
, float& outlineRadius
,
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
);
347 textRect
= fVideoBounds
;
348 textRect
.OffsetBy(outlineRadius
, outlineRadius
);
350 fTextView
->SetText(NULL
);
351 fTextView
->SetFontAndColor(&font
, B_FONT_ALL
, &color
);
353 fTextView
->Insert(" ");
354 parse_text(fText
, fTextView
, font
, color
, true);
356 font
.SetFalseBoldWidth(outlineRadius
);
357 fShadowTextView
->ForceFontAliasing(overlayMode
);
358 fShadowTextView
->SetText(NULL
);
359 fShadowTextView
->SetFontAndColor(&font
, B_FONT_ALL
, &shadow
);
361 fShadowTextView
->Insert(" ");
362 parse_text(fText
, fShadowTextView
, font
, shadow
, false);
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
);
370 textRect
= fTextView
->TextRect();
371 textRect
.InsetBy(-outlineRadius
, -outlineRadius
);
372 textRect
.OffsetTo(B_ORIGIN
);
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
;