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