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
SubtitleBitmap()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
~SubtitleBitmap()36 SubtitleBitmap::~SubtitleBitmap()
37 {
38 delete fBitmap;
39 delete fTextView;
40 delete fShadowTextView;
41 }
42
43
44 bool
SetText(const char * text)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
SetVideoBounds(BRect bounds)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
SetOverlayMode(bool overlayMode)71 SubtitleBitmap::SetOverlayMode(bool overlayMode)
72 {
73 if (overlayMode == fOverlayMode)
74 return;
75
76 fOverlayMode = overlayMode;
77
78 _GenerateBitmap();
79 }
80
81
82 void
SetCharsPerLine(float charsPerLine)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*
Bitmap() const96 SubtitleBitmap::Bitmap() const
97 {
98 return fBitmap;
99 }
100
101
102 void
_GenerateBitmap()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 {
ParseStateParseState168 ParseState(rgb_color color)
169 :
170 color(color),
171 bold(false),
172 italic(false),
173 underlined(false),
174
175 previous(NULL)
176 {
177 }
178
ParseStateParseState179 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
find_next_tag(const BString & string,int32 & tagPos,int32 & tagLength,ParseState * & state)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
apply_state(BTextView * textView,const ParseState * state,BFont font,bool changeColor)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
parse_text(const BString & string,BTextView * textView,const BFont & font,const rgb_color & color,bool changeColor)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
_InsertText(BRect & textRect,float & outlineRadius,bool overlayMode)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