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