xref: /haiku/src/apps/mediaplayer/interface/SubtitleBitmap.cpp (revision 909af08f4328301fbdef1ffb41f566c3b5bec0c7)
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