Shaka Packager SDK
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 
17 namespace shaka {
18 namespace media {
19 
20 namespace {
21 
22 const uint16_t kDefaultWidth = 720;
23 const uint16_t kDefaultHeight = 576;
24 const RgbaColor kTransparent{0, 0, 0, 0};
25 
26 struct 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 
34 void 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 
39 void PngFlushData(png_structp png) {}
40 
41 bool 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 
49 bool 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 
99 SubtitleComposer::SubtitleComposer()
100  : display_width_(kDefaultWidth), display_height_(kDefaultHeight) {}
101 
102 SubtitleComposer::~SubtitleComposer() {}
103 
104 void SubtitleComposer::SetDisplaySize(uint16_t width, uint16_t height) {
105  display_width_ = width;
106  display_height_ = height;
107 }
108 
109 bool 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 
130 bool 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 
145 bool 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 
169 DvbImageColorSpace* SubtitleComposer::GetColorSpace(uint8_t color_space_id) {
170  return &color_spaces_[color_space_id];
171 }
172 
173 DvbImageColorSpace* 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 
183 DvbImageBuilder* 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 
207 bool 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 
250 void 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.
Definition: crypto_flags.cc:66