xref: /haiku/src/apps/mediaplayer/interface/SubtitleBitmap.cpp (revision 25fb0e67fdb540a8fa867b3dbbe40ae7207e903a)
1*25fb0e67SStephan Aßmus /*
2*25fb0e67SStephan Aßmus  * Copyright 2010, Stephan Aßmus <superstippi@gmx.de>.
3*25fb0e67SStephan Aßmus  * Distributed under the terms of the MIT License.
4*25fb0e67SStephan Aßmus  */
5*25fb0e67SStephan Aßmus 
6*25fb0e67SStephan Aßmus 
7*25fb0e67SStephan Aßmus #include "SubtitleBitmap.h"
8*25fb0e67SStephan Aßmus 
9*25fb0e67SStephan Aßmus #include <stdio.h>
10*25fb0e67SStephan Aßmus 
11*25fb0e67SStephan Aßmus #include <Bitmap.h>
12*25fb0e67SStephan Aßmus #include <TextView.h>
13*25fb0e67SStephan Aßmus 
14*25fb0e67SStephan Aßmus 
15*25fb0e67SStephan Aßmus SubtitleBitmap::SubtitleBitmap()
16*25fb0e67SStephan Aßmus 	:
17*25fb0e67SStephan Aßmus 	fBitmap(NULL),
18*25fb0e67SStephan Aßmus 	fTextView(new BTextView("offscreen text")),
19*25fb0e67SStephan Aßmus 	fShadowTextView(new BTextView("offscreen text shadow"))
20*25fb0e67SStephan Aßmus {
21*25fb0e67SStephan Aßmus 	fTextView->SetStylable(true);
22*25fb0e67SStephan Aßmus 	fTextView->MakeEditable(false);
23*25fb0e67SStephan Aßmus 	fTextView->SetWordWrap(false);
24*25fb0e67SStephan Aßmus 	fTextView->SetAlignment(B_ALIGN_CENTER);
25*25fb0e67SStephan Aßmus 
26*25fb0e67SStephan Aßmus 	fShadowTextView->SetStylable(true);
27*25fb0e67SStephan Aßmus 	fShadowTextView->MakeEditable(false);
28*25fb0e67SStephan Aßmus 	fShadowTextView->SetWordWrap(false);
29*25fb0e67SStephan Aßmus 	fShadowTextView->SetAlignment(B_ALIGN_CENTER);
30*25fb0e67SStephan Aßmus }
31*25fb0e67SStephan Aßmus 
32*25fb0e67SStephan Aßmus 
33*25fb0e67SStephan Aßmus SubtitleBitmap::~SubtitleBitmap()
34*25fb0e67SStephan Aßmus {
35*25fb0e67SStephan Aßmus 	delete fBitmap;
36*25fb0e67SStephan Aßmus 	delete fTextView;
37*25fb0e67SStephan Aßmus 	delete fShadowTextView;
38*25fb0e67SStephan Aßmus }
39*25fb0e67SStephan Aßmus 
40*25fb0e67SStephan Aßmus 
41*25fb0e67SStephan Aßmus void
42*25fb0e67SStephan Aßmus SubtitleBitmap::SetText(const char* text)
43*25fb0e67SStephan Aßmus {
44*25fb0e67SStephan Aßmus 	if (text == fText)
45*25fb0e67SStephan Aßmus 		return;
46*25fb0e67SStephan Aßmus 
47*25fb0e67SStephan Aßmus 	fText = text;
48*25fb0e67SStephan Aßmus 
49*25fb0e67SStephan Aßmus 	_GenerateBitmap();
50*25fb0e67SStephan Aßmus }
51*25fb0e67SStephan Aßmus 
52*25fb0e67SStephan Aßmus 
53*25fb0e67SStephan Aßmus void
54*25fb0e67SStephan Aßmus SubtitleBitmap::SetVideoBounds(BRect bounds)
55*25fb0e67SStephan Aßmus {
56*25fb0e67SStephan Aßmus 	if (bounds == fVideoBounds)
57*25fb0e67SStephan Aßmus 		return;
58*25fb0e67SStephan Aßmus 
59*25fb0e67SStephan Aßmus 	fVideoBounds = bounds;
60*25fb0e67SStephan Aßmus 
61*25fb0e67SStephan Aßmus 	_GenerateBitmap();
62*25fb0e67SStephan Aßmus }
63*25fb0e67SStephan Aßmus 
64*25fb0e67SStephan Aßmus 
65*25fb0e67SStephan Aßmus const BBitmap*
66*25fb0e67SStephan Aßmus SubtitleBitmap::Bitmap() const
67*25fb0e67SStephan Aßmus {
68*25fb0e67SStephan Aßmus 	return fBitmap;
69*25fb0e67SStephan Aßmus }
70*25fb0e67SStephan Aßmus 
71*25fb0e67SStephan Aßmus 
72*25fb0e67SStephan Aßmus void
73*25fb0e67SStephan Aßmus SubtitleBitmap::_GenerateBitmap()
74*25fb0e67SStephan Aßmus {
75*25fb0e67SStephan Aßmus 	if (!fVideoBounds.IsValid())
76*25fb0e67SStephan Aßmus 		return;
77*25fb0e67SStephan Aßmus 
78*25fb0e67SStephan Aßmus 	delete fBitmap;
79*25fb0e67SStephan Aßmus 
80*25fb0e67SStephan Aßmus 	BRect bounds = _InsertText();
81*25fb0e67SStephan Aßmus 
82*25fb0e67SStephan Aßmus 	fBitmap = new BBitmap(bounds, B_BITMAP_ACCEPTS_VIEWS, B_RGBA32);
83*25fb0e67SStephan Aßmus 	memset(fBitmap->Bits(), 0, fBitmap->BitsLength());
84*25fb0e67SStephan Aßmus 
85*25fb0e67SStephan Aßmus 	if (fBitmap->Lock()) {
86*25fb0e67SStephan Aßmus 		fBitmap->AddChild(fShadowTextView);
87*25fb0e67SStephan Aßmus 		fShadowTextView->ResizeTo(bounds.Width(), bounds.Height());
88*25fb0e67SStephan Aßmus 
89*25fb0e67SStephan Aßmus 		fShadowTextView->SetViewColor(0, 0, 0, 0);
90*25fb0e67SStephan Aßmus 		fShadowTextView->SetDrawingMode(B_OP_ALPHA);
91*25fb0e67SStephan Aßmus 		fShadowTextView->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_COMPOSITE);
92*25fb0e67SStephan Aßmus 
93*25fb0e67SStephan Aßmus 		fShadowTextView->PushState();
94*25fb0e67SStephan Aßmus 		fShadowTextView->Draw(bounds);
95*25fb0e67SStephan Aßmus 		fShadowTextView->PopState();
96*25fb0e67SStephan Aßmus 
97*25fb0e67SStephan Aßmus 		fShadowTextView->Sync();
98*25fb0e67SStephan Aßmus 		fShadowTextView->RemoveSelf();
99*25fb0e67SStephan Aßmus 
100*25fb0e67SStephan Aßmus 		fBitmap->AddChild(fTextView);
101*25fb0e67SStephan Aßmus 		fTextView->ResizeTo(bounds.Width(), bounds.Height());
102*25fb0e67SStephan Aßmus 
103*25fb0e67SStephan Aßmus 		fTextView->SetViewColor(0, 0, 0, 0);
104*25fb0e67SStephan Aßmus 		fTextView->SetDrawingMode(B_OP_ALPHA);
105*25fb0e67SStephan Aßmus 		fTextView->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_COMPOSITE);
106*25fb0e67SStephan Aßmus 
107*25fb0e67SStephan Aßmus 		fTextView->PushState();
108*25fb0e67SStephan Aßmus 		fTextView->Draw(bounds);
109*25fb0e67SStephan Aßmus 		fTextView->PopState();
110*25fb0e67SStephan Aßmus 
111*25fb0e67SStephan Aßmus 		fTextView->Sync();
112*25fb0e67SStephan Aßmus 		fTextView->RemoveSelf();
113*25fb0e67SStephan Aßmus 
114*25fb0e67SStephan Aßmus 		fBitmap->Unlock();
115*25fb0e67SStephan Aßmus 	}
116*25fb0e67SStephan Aßmus }
117*25fb0e67SStephan Aßmus 
118*25fb0e67SStephan Aßmus 
119*25fb0e67SStephan Aßmus struct ParseState {
120*25fb0e67SStephan Aßmus 	ParseState()
121*25fb0e67SStephan Aßmus 		:
122*25fb0e67SStephan Aßmus 		color((rgb_color){ 255, 255, 255, 255 }),
123*25fb0e67SStephan Aßmus 		bold(false),
124*25fb0e67SStephan Aßmus 		italic(false),
125*25fb0e67SStephan Aßmus 		underlined(false),
126*25fb0e67SStephan Aßmus 
127*25fb0e67SStephan Aßmus 		previous(NULL)
128*25fb0e67SStephan Aßmus 	{
129*25fb0e67SStephan Aßmus 	}
130*25fb0e67SStephan Aßmus 
131*25fb0e67SStephan Aßmus 	ParseState(ParseState* previous)
132*25fb0e67SStephan Aßmus 		:
133*25fb0e67SStephan Aßmus 		color(previous->color),
134*25fb0e67SStephan Aßmus 		bold(previous->bold),
135*25fb0e67SStephan Aßmus 		italic(previous->italic),
136*25fb0e67SStephan Aßmus 		underlined(previous->underlined),
137*25fb0e67SStephan Aßmus 
138*25fb0e67SStephan Aßmus 		previous(previous)
139*25fb0e67SStephan Aßmus 	{
140*25fb0e67SStephan Aßmus 	}
141*25fb0e67SStephan Aßmus 	rgb_color	color;
142*25fb0e67SStephan Aßmus 	bool		bold;
143*25fb0e67SStephan Aßmus 	bool		italic;
144*25fb0e67SStephan Aßmus 	bool		underlined;
145*25fb0e67SStephan Aßmus 
146*25fb0e67SStephan Aßmus 	ParseState*	previous;
147*25fb0e67SStephan Aßmus };
148*25fb0e67SStephan Aßmus 
149*25fb0e67SStephan Aßmus 
150*25fb0e67SStephan Aßmus static bool
151*25fb0e67SStephan Aßmus find_next_tag(const BString& string, int32& tagPos, int32& tagLength,
152*25fb0e67SStephan Aßmus 	ParseState*& state)
153*25fb0e67SStephan Aßmus {
154*25fb0e67SStephan Aßmus 	static const char* kTags[] = {
155*25fb0e67SStephan Aßmus 		"<b>", "</b>",
156*25fb0e67SStephan Aßmus 		"<i>", "</i>",
157*25fb0e67SStephan Aßmus 		"<u>", "</u>",
158*25fb0e67SStephan Aßmus 		"<font color=\"#", "</font>"
159*25fb0e67SStephan Aßmus 	};
160*25fb0e67SStephan Aßmus 	static const int32 kTagCount = sizeof(kTags) / sizeof(const char*);
161*25fb0e67SStephan Aßmus 
162*25fb0e67SStephan Aßmus 	int32 current = tagPos;
163*25fb0e67SStephan Aßmus 	tagPos = string.Length();
164*25fb0e67SStephan Aßmus 	tagLength = 0;
165*25fb0e67SStephan Aßmus 
166*25fb0e67SStephan Aßmus 	BString tag;
167*25fb0e67SStephan Aßmus 	for (int32 i = 0; i < kTagCount; i++) {
168*25fb0e67SStephan Aßmus 		int32 nextTag = string.FindFirst(kTags[i], current);
169*25fb0e67SStephan Aßmus 		if (nextTag >= current && nextTag < tagPos) {
170*25fb0e67SStephan Aßmus 			tagPos = nextTag;
171*25fb0e67SStephan Aßmus 			tag = kTags[i];
172*25fb0e67SStephan Aßmus 		}
173*25fb0e67SStephan Aßmus 	}
174*25fb0e67SStephan Aßmus 
175*25fb0e67SStephan Aßmus 	if (tag != "") {
176*25fb0e67SStephan Aßmus 		tagLength = tag.Length();
177*25fb0e67SStephan Aßmus 		if (tag == "<b>") {
178*25fb0e67SStephan Aßmus 			state = new ParseState(state);
179*25fb0e67SStephan Aßmus 			state->bold = true;
180*25fb0e67SStephan Aßmus 		} else if (tag == "<i>") {
181*25fb0e67SStephan Aßmus 			state = new ParseState(state);
182*25fb0e67SStephan Aßmus 			state->italic = true;
183*25fb0e67SStephan Aßmus 		} else if (tag == "<u>") {
184*25fb0e67SStephan Aßmus 			state = new ParseState(state);
185*25fb0e67SStephan Aßmus 			state->underlined = true;
186*25fb0e67SStephan Aßmus 		} else if (tag == "<font color=\"#") {
187*25fb0e67SStephan Aßmus 			state = new ParseState(state);
188*25fb0e67SStephan Aßmus 			char number[16];
189*25fb0e67SStephan Aßmus 			snprintf(number, sizeof(number), "0x%.6s",
190*25fb0e67SStephan Aßmus 				string.String() + tagPos + tag.Length());
191*25fb0e67SStephan Aßmus 			int colorInt;
192*25fb0e67SStephan Aßmus 			if (sscanf(number, "%x", &colorInt) == 1) {
193*25fb0e67SStephan Aßmus 				state->color.red = (colorInt & 0xff0000) >> 16;
194*25fb0e67SStephan Aßmus 				state->color.green = (colorInt & 0x00ff00) >> 8;
195*25fb0e67SStephan Aßmus 				state->color.blue = (colorInt & 0x0000ff);
196*25fb0e67SStephan Aßmus 				tagLength += 8;
197*25fb0e67SStephan Aßmus 			}
198*25fb0e67SStephan Aßmus 		} else if (tag == "</b>" || tag == "</i>" || tag == "</u>"
199*25fb0e67SStephan Aßmus 			|| tag == "</font>") {
200*25fb0e67SStephan Aßmus 			// Pop state
201*25fb0e67SStephan Aßmus 			if (state->previous != NULL) {
202*25fb0e67SStephan Aßmus 				ParseState* oldState = state;
203*25fb0e67SStephan Aßmus 				state = state->previous;
204*25fb0e67SStephan Aßmus 				delete oldState;
205*25fb0e67SStephan Aßmus 			}
206*25fb0e67SStephan Aßmus 		}
207*25fb0e67SStephan Aßmus 		return true;
208*25fb0e67SStephan Aßmus 	}
209*25fb0e67SStephan Aßmus 	return false;
210*25fb0e67SStephan Aßmus }
211*25fb0e67SStephan Aßmus 
212*25fb0e67SStephan Aßmus 
213*25fb0e67SStephan Aßmus static void
214*25fb0e67SStephan Aßmus apply_state(BTextView* textView, const ParseState* state, BFont font,
215*25fb0e67SStephan Aßmus 	bool changeColor)
216*25fb0e67SStephan Aßmus {
217*25fb0e67SStephan Aßmus 	uint16 face = 0;
218*25fb0e67SStephan Aßmus 	if (state->bold || state->italic || state->underlined) {
219*25fb0e67SStephan Aßmus 		if (state->bold)
220*25fb0e67SStephan Aßmus 			face |= B_BOLD_FACE;
221*25fb0e67SStephan Aßmus 		if (state->italic)
222*25fb0e67SStephan Aßmus 			face |= B_ITALIC_FACE;
223*25fb0e67SStephan Aßmus 		if (state->underlined)
224*25fb0e67SStephan Aßmus 			face |= B_UNDERSCORE_FACE;
225*25fb0e67SStephan Aßmus 	} else
226*25fb0e67SStephan Aßmus 		face = B_REGULAR_FACE;
227*25fb0e67SStephan Aßmus 	font.SetFace(face);
228*25fb0e67SStephan Aßmus 	if (changeColor)
229*25fb0e67SStephan Aßmus 		textView->SetFontAndColor(&font, B_FONT_ALL, &state->color);
230*25fb0e67SStephan Aßmus 	else
231*25fb0e67SStephan Aßmus 		textView->SetFontAndColor(&font, B_FONT_ALL, NULL);
232*25fb0e67SStephan Aßmus }
233*25fb0e67SStephan Aßmus 
234*25fb0e67SStephan Aßmus 
235*25fb0e67SStephan Aßmus static void
236*25fb0e67SStephan Aßmus parse_text(const BString& string, BTextView* textView, const BFont& font,
237*25fb0e67SStephan Aßmus 	bool changeColor)
238*25fb0e67SStephan Aßmus {
239*25fb0e67SStephan Aßmus 	ParseState rootState;
240*25fb0e67SStephan Aßmus 	ParseState* state = &rootState;
241*25fb0e67SStephan Aßmus 
242*25fb0e67SStephan Aßmus 	int32 pos = 0;
243*25fb0e67SStephan Aßmus 	while (pos < string.Length()) {
244*25fb0e67SStephan Aßmus 		int32 nextPos = pos;
245*25fb0e67SStephan Aßmus 		int32 tagLength;
246*25fb0e67SStephan Aßmus 		bool stateChanged = find_next_tag(string, nextPos, tagLength, state);
247*25fb0e67SStephan Aßmus 		if (nextPos > pos) {
248*25fb0e67SStephan Aßmus 			BString subString;
249*25fb0e67SStephan Aßmus 			string.CopyInto(subString, pos, nextPos - pos);
250*25fb0e67SStephan Aßmus 			textView->Insert(subString.String());
251*25fb0e67SStephan Aßmus 		}
252*25fb0e67SStephan Aßmus 		pos = nextPos + tagLength;
253*25fb0e67SStephan Aßmus 		if (stateChanged)
254*25fb0e67SStephan Aßmus 			apply_state(textView, state, font, changeColor);
255*25fb0e67SStephan Aßmus 	}
256*25fb0e67SStephan Aßmus }
257*25fb0e67SStephan Aßmus 
258*25fb0e67SStephan Aßmus 
259*25fb0e67SStephan Aßmus BRect
260*25fb0e67SStephan Aßmus SubtitleBitmap::_InsertText()
261*25fb0e67SStephan Aßmus {
262*25fb0e67SStephan Aßmus 	BFont font(be_plain_font);
263*25fb0e67SStephan Aßmus 	float fontSize = ceilf((fVideoBounds.Width() * 0.9) / 35);
264*25fb0e67SStephan Aßmus 	float falseBoldWidth = ceilf(fontSize / 28.0);
265*25fb0e67SStephan Aßmus 	font.SetSize(fontSize);
266*25fb0e67SStephan Aßmus 
267*25fb0e67SStephan Aßmus 	rgb_color shadow;
268*25fb0e67SStephan Aßmus 	shadow.red = 0;
269*25fb0e67SStephan Aßmus 	shadow.green = 0;
270*25fb0e67SStephan Aßmus 	shadow.blue = 0;
271*25fb0e67SStephan Aßmus 	shadow.alpha = 200;
272*25fb0e67SStephan Aßmus 
273*25fb0e67SStephan Aßmus 	rgb_color color;
274*25fb0e67SStephan Aßmus 	color.red = 255;
275*25fb0e67SStephan Aßmus 	color.green = 255;
276*25fb0e67SStephan Aßmus 	color.blue = 255;
277*25fb0e67SStephan Aßmus 	color.alpha = 240;
278*25fb0e67SStephan Aßmus 
279*25fb0e67SStephan Aßmus 	BRect textRect = fVideoBounds;
280*25fb0e67SStephan Aßmus 	textRect.OffsetBy(falseBoldWidth, falseBoldWidth);
281*25fb0e67SStephan Aßmus 
282*25fb0e67SStephan Aßmus 	fTextView->SetText(NULL);
283*25fb0e67SStephan Aßmus 	fTextView->SetFontAndColor(&font, B_FONT_ALL, &color);
284*25fb0e67SStephan Aßmus 
285*25fb0e67SStephan Aßmus 	fTextView->Insert(" ");
286*25fb0e67SStephan Aßmus 	parse_text(fText, fTextView, font, true);
287*25fb0e67SStephan Aßmus 
288*25fb0e67SStephan Aßmus 	font.SetFalseBoldWidth(falseBoldWidth);
289*25fb0e67SStephan Aßmus 	fShadowTextView->SetText(NULL);
290*25fb0e67SStephan Aßmus 	fShadowTextView->SetFontAndColor(&font, B_FONT_ALL, &shadow);
291*25fb0e67SStephan Aßmus 
292*25fb0e67SStephan Aßmus 	fShadowTextView->Insert(" ");
293*25fb0e67SStephan Aßmus 	parse_text(fText, fShadowTextView, font, false);
294*25fb0e67SStephan Aßmus 
295*25fb0e67SStephan Aßmus 	// This causes the BTextView to calculate the layout of the text
296*25fb0e67SStephan Aßmus 	fTextView->SetTextRect(BRect(0, 0, 0, 0));
297*25fb0e67SStephan Aßmus 	fTextView->SetTextRect(textRect);
298*25fb0e67SStephan Aßmus 	fShadowTextView->SetTextRect(BRect(0, 0, 0, 0));
299*25fb0e67SStephan Aßmus 	fShadowTextView->SetTextRect(textRect);
300*25fb0e67SStephan Aßmus 
301*25fb0e67SStephan Aßmus 	textRect = fTextView->TextRect();
302*25fb0e67SStephan Aßmus 	textRect.InsetBy(-falseBoldWidth, -falseBoldWidth);
303*25fb0e67SStephan Aßmus 	textRect.OffsetTo(B_ORIGIN);
304*25fb0e67SStephan Aßmus 	return textRect;
305*25fb0e67SStephan Aßmus }
306*25fb0e67SStephan Aßmus 
307*25fb0e67SStephan Aßmus 
308*25fb0e67SStephan Aßmus 
309