Shaka Packager SDK
Loading...
Searching...
No Matches
subtitle_composer.cc
1// Copyright 2020 Google LLC. All rights reserved.
2//
3// Use of this source code is governed by a BSD-style
4// license that can be found in the LICENSE file or at
5// https://developers.google.com/open-source/licenses/bsd
6
7#include <packager/media/formats/dvb/subtitle_composer.h>
8
9#include <cstring>
10
11#include <absl/log/check.h>
12#include <absl/log/log.h>
13#include <png.h>
14
15#include <packager/macros/logging.h>
16
17namespace shaka {
18namespace media {
19
20namespace {
21
22const uint16_t kDefaultWidth = 720;
23const uint16_t kDefaultHeight = 576;
24const RgbaColor kTransparent{0, 0, 0, 0};
25
26struct PngFreeHelper {
27 PngFreeHelper(png_structp* png, png_infop* info) : png(png), info(info) {}
28 ~PngFreeHelper() { png_destroy_write_struct(png, info); }
29
30 png_structp* png;
31 png_infop* info;
32};
33
34void PngWriteData(png_structp png, png_bytep data, png_size_t length) {
35 auto* output = reinterpret_cast<std::vector<uint8_t>*>(png_get_io_ptr(png));
36 output->insert(output->end(), data, data + length);
37}
38
39void PngFlushData(png_structp png) {}
40
41bool IsTransparent(const RgbaColor* colors, uint16_t width, uint16_t height) {
42 for (size_t i = 0; i < static_cast<size_t>(width) * height; i++) {
43 if (colors[i].a != 0)
44 return false;
45 }
46 return true;
47}
48
49bool GetImageData(const DvbImageBuilder* image,
50 std::vector<uint8_t>* data,
51 uint16_t* width,
52 uint16_t* height) {
53 // CAREFUL in this method since this uses long-jumps. A long-jump causes the
54 // execution to jump to another point *without executing returns*. This
55 // causes C++ objects to not get destroyed. This also causes the same code to
56 // be executed twice, so C++ objects can be initialized twice.
57 //
58 // So long as we don't create C++ objects after the long-jump point,
59 // everything should work fine. If we early-return after the long-jump, the
60 // destructors will still be called; if we long-jump, we won't call the
61 // constructors since we're past that point.
62 auto png =
63 png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
64 auto info = png_create_info_struct(png);
65 PngFreeHelper helper(&png, &info);
66 if (!png || !info) {
67 LOG(ERROR) << "Error creating libpng struct";
68 return false;
69 }
70 if (setjmp(png_jmpbuf(png))) {
71 // If any png_* functions fail, the code will jump back to here.
72 LOG(ERROR) << "Error writing PNG image";
73 return false;
74 }
75 png_set_write_fn(png, data, &PngWriteData, &PngFlushData);
76
77 const RgbaColor* pixels;
78 if (!image->GetPixels(&pixels, width, height))
79 return false;
80 if (IsTransparent(pixels, *width, *height))
81 return true; // Skip empty/transparent images.
82 png_set_IHDR(png, info, *width, *height, 8, PNG_COLOR_TYPE_RGBA,
83 PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
84 PNG_FILTER_TYPE_BASE);
85 png_write_info(png, info);
86
87 const uint8_t* in_data = reinterpret_cast<const uint8_t*>(pixels);
88 for (size_t y = 0; y < *height; y++) {
89 size_t offset = image->max_width() * y * sizeof(RgbaColor);
90 png_write_row(png, in_data + offset);
91 }
92 png_write_end(png, nullptr);
93
94 return true;
95}
96
97} // namespace
98
99SubtitleComposer::SubtitleComposer()
100 : display_width_(kDefaultWidth), display_height_(kDefaultHeight) {}
101
102SubtitleComposer::~SubtitleComposer() {}
103
104void SubtitleComposer::SetDisplaySize(uint16_t width, uint16_t height) {
105 display_width_ = width;
106 display_height_ = height;
107}
108
109bool SubtitleComposer::SetRegionInfo(uint8_t region_id,
110 uint8_t color_space_id,
111 uint16_t width,
112 uint16_t height) {
113 auto* region = &regions_[region_id];
114 if (region->x + width > display_width_ ||
115 region->y + height > display_height_) {
116 LOG(ERROR) << "DVB-sub region won't fit within display";
117 return false;
118 }
119 if (width == 0 || height == 0) {
120 LOG(ERROR) << "DVB-sub width/height cannot be 0";
121 return false;
122 }
123
124 region->width = width;
125 region->height = height;
126 region->color_space = &color_spaces_[color_space_id];
127 return true;
128}
129
130bool SubtitleComposer::SetRegionPosition(uint8_t region_id,
131 uint16_t x,
132 uint16_t y) {
133 auto* region = &regions_[region_id];
134 if (x + region->width > display_width_ ||
135 y + region->height > display_height_) {
136 LOG(ERROR) << "DVB-sub region won't fit within display";
137 return false;
138 }
139
140 region->x = x;
141 region->y = y;
142 return true;
143}
144
145bool SubtitleComposer::SetObjectInfo(uint16_t object_id,
146 uint8_t region_id,
147 uint16_t x,
148 uint16_t y,
149 int default_color_code) {
150 auto region = regions_.find(region_id);
151 if (region == regions_.end()) {
152 LOG(ERROR) << "Unknown DVB-sub region: " << (int)region_id
153 << ", in object: " << object_id;
154 return false;
155 }
156 if (x >= region->second.width || y >= region->second.height) {
157 LOG(ERROR) << "DVB-sub object is outside region: " << object_id;
158 return false;
159 }
160
161 auto* object = &objects_[object_id];
162 object->region = &region->second;
163 object->default_color_code = default_color_code;
164 object->x = x;
165 object->y = y;
166 return true;
167}
168
169DvbImageColorSpace* SubtitleComposer::GetColorSpace(uint8_t color_space_id) {
170 return &color_spaces_[color_space_id];
171}
172
173DvbImageColorSpace* SubtitleComposer::GetColorSpaceForObject(
174 uint16_t object_id) {
175 auto info = objects_.find(object_id);
176 if (info == objects_.end()) {
177 LOG(ERROR) << "Unknown DVB-sub object: " << object_id;
178 return nullptr;
179 }
180 return info->second.region->color_space;
181}
182
183DvbImageBuilder* SubtitleComposer::GetObjectImage(uint16_t object_id) {
184 auto it = images_.find(object_id);
185 if (it == images_.end()) {
186 auto info = objects_.find(object_id);
187 if (info == objects_.end()) {
188 LOG(ERROR) << "Unknown DVB-sub object: " << object_id;
189 return nullptr;
190 }
191
192 auto color = info->second.default_color_code < 0
193 ? kTransparent
194 : info->second.region->color_space->GetColor(
195 BitDepth::k8Bit, info->second.default_color_code);
196 it = images_
197 .emplace(std::piecewise_construct, std::make_tuple(object_id),
198 std::make_tuple(
199 info->second.region->color_space, color,
200 info->second.region->width - info->second.region->x,
201 info->second.region->height - info->second.region->y))
202 .first;
203 }
204 return &it->second;
205}
206
207bool SubtitleComposer::GetSamples(
208 int64_t start,
209 int64_t end,
210 std::vector<std::shared_ptr<TextSample>>* samples) const {
211 for (const auto& pair : objects_) {
212 auto it = images_.find(pair.first);
213 if (it == images_.end()) {
214 LOG(WARNING) << "DVB-sub object " << pair.first
215 << " doesn't include object data";
216 continue;
217 }
218
219 uint16_t width, height;
220 std::vector<uint8_t> image_data;
221 if (!GetImageData(&it->second, &image_data, &width, &height))
222 return false;
223 if (image_data.empty()) {
224 VLOG(1) << "Skipping transparent object";
225 continue;
226 }
227 TextFragment body({}, image_data);
228 DCHECK_LE(width, display_width_);
229 DCHECK_LE(height, display_height_);
230
231 TextSettings settings;
232 settings.position.emplace(
233 (pair.second.x + pair.second.region->x) * 100.0f / display_width_,
234 TextUnitType::kPercent);
235 settings.line.emplace(
236 (pair.second.y + pair.second.region->y) * 100.0f / display_height_,
237 TextUnitType::kPercent);
238 settings.width.emplace(width * 100.0f / display_width_,
239 TextUnitType::kPercent);
240 settings.height.emplace(height * 100.0f / display_height_,
241 TextUnitType::kPercent);
242
243 samples->emplace_back(
244 std::make_shared<TextSample>("", start, end, settings, body));
245 }
246
247 return true;
248}
249
250void SubtitleComposer::ClearObjects() {
251 regions_.clear();
252 objects_.clear();
253 images_.clear();
254}
255
256} // namespace media
257} // namespace shaka
All the methods that are virtual are virtual for mocking.