Merge master.
[dcpomatic.git] / src / lib / render_subtitles.cc
1 /*
2     Copyright (C) 2014 Carl Hetherington <cth@carlh.net>
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18 */
19
20 #include <cairomm/cairomm.h>
21 #include <pangomm.h>
22 #include "render_subtitles.h"
23 #include "types.h"
24 #include "image.h"
25
26 using std::list;
27 using std::cout;
28 using std::string;
29 using std::min;
30 using std::max;
31 using std::pair;
32 using boost::shared_ptr;
33 using boost::optional;
34
35 static int
36 calculate_position (dcp::VAlign v_align, double v_position, int target_height, int offset)
37 {
38         switch (v_align) {
39         case dcp::TOP:
40                 return (v_position / 100) * target_height - offset;
41         case dcp::CENTER:
42                 return (0.5 + v_position / 100) * target_height - offset;
43         case dcp::BOTTOM:
44                 return (1.0 - v_position / 100) * target_height - offset;
45         }
46
47         return 0;
48 }
49
50 PositionImage
51 render_subtitles (list<dcp::SubtitleString> subtitles, dcp::Size target)
52 {
53         if (subtitles.empty ()) {
54                 return PositionImage ();
55         }
56
57         /* Estimate height that the subtitle image needs to be */
58         optional<int> top;
59         optional<int> bottom;
60         for (list<dcp::SubtitleString>::const_iterator i = subtitles.begin(); i != subtitles.end(); ++i) {
61                 int const b = calculate_position (i->v_align(), i->v_position(), target.height, 0);
62                 int const t = b - i->size() * target.height / (11 * 72);
63
64                 top = min (top.get_value_or (t), t);
65                 bottom = max (bottom.get_value_or (b), b);
66         }
67
68         top = top.get() - 32;
69         bottom = bottom.get() + 32;
70
71         shared_ptr<Image> image (new Image (PIX_FMT_RGBA, dcp::Size (target.width, bottom.get() - top.get ()), false));
72         image->make_black ();
73
74         Cairo::RefPtr<Cairo::ImageSurface> surface = Cairo::ImageSurface::create (
75                 image->data()[0],
76                 Cairo::FORMAT_ARGB32,
77                 image->size().width,
78                 image->size().height,
79                 Cairo::ImageSurface::format_stride_for_width (Cairo::FORMAT_ARGB32, image->size().width)
80                 );
81         
82         Cairo::RefPtr<Cairo::Context> context = Cairo::Context::create (surface);
83         Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create (context);
84
85         layout->set_width (image->size().width * PANGO_SCALE);
86         layout->set_alignment (Pango::ALIGN_CENTER);
87
88         context->set_line_width (1);
89
90         for (list<dcp::SubtitleString>::const_iterator i = subtitles.begin(); i != subtitles.end(); ++i) {
91                 string f = i->font ();
92                 if (f.empty ()) {
93                         f = "Arial";
94                 }
95                 Pango::FontDescription font (f);
96                 font.set_absolute_size (i->size_in_pixels (target.height) * PANGO_SCALE);
97                 if (i->italic ()) {
98                         font.set_style (Pango::STYLE_ITALIC);
99                 }
100                 layout->set_font_description (font);
101                 layout->set_text (i->text ());
102
103                 /* Compute fade factor */
104                 /* XXX */
105                 float fade_factor = 1;
106 #if 0           
107                 dcp::Time now (time * 1000 / (4 * TIME_HZ));
108                 dcp::Time end_fade_up = i->in() + i->fade_up_time ();
109                 dcp::Time start_fade_down = i->out() - i->fade_down_time ();
110                 if (now < end_fade_up) {
111                         fade_factor = (now - i->in()) / i->fade_up_time();
112                 } else if (now > start_fade_down) {
113                         fade_factor = 1.0 - ((now - start_fade_down) / i->fade_down_time ());
114                 }
115 #endif          
116
117                 layout->update_from_cairo_context (context);
118                 
119                 /* Work out position */
120
121                 int const x = 0;
122                 int const y = calculate_position (i->v_align (), i->v_position (), target.height, (layout->get_baseline() / PANGO_SCALE) + top.get ());
123
124                 if (i->effect() == dcp::SHADOW) {
125                         /* Drop-shadow effect */
126                         dcp::Color const ec = i->effect_color ();
127                         context->set_source_rgba (float(ec.r) / 255, float(ec.g) / 255, float(ec.b) / 255, fade_factor);
128                         context->move_to (x + 4, y + 4);
129                         layout->add_to_cairo_context (context);
130                         context->fill ();
131                 }
132
133                 /* The actual subtitle */
134                 context->move_to (x, y);
135                 dcp::Color const c = i->color ();
136                 context->set_source_rgba (float(c.r) / 255, float(c.g) / 255, float(c.b) / 255, fade_factor);
137                 layout->add_to_cairo_context (context);
138                 context->fill ();
139
140                 if (i->effect() == dcp::BORDER) {
141                         /* Border effect */
142                         context->move_to (x, y);
143                         dcp::Color ec = i->effect_color ();
144                         context->set_source_rgba (float(ec.r) / 255, float(ec.g) / 255, float(ec.b) / 255, fade_factor);
145                         layout->add_to_cairo_context (context);
146                         context->stroke ();
147                 }
148         }
149
150         return PositionImage (image, Position<int> (0, top.get ()));
151 }
152