OpenShot Library | libopenshot 1.1.1
Loading...
Searching...
No Matches
TrackedObjectBBox.cpp
Go to the documentation of this file.
1
10// Copyright (c) 2008-2019 OpenShot Studios, LLC
11//
12// SPDX-License-Identifier: LGPL-3.0-or-later
13
14#include <algorithm>
15#include <cmath>
16#include <fstream>
17
18#include "TrackedObjectBBox.h"
19
20#include "Clip.h"
21#include "TimelineBase.h"
22
23#include "trackerdata.pb.h"
24#include <google/protobuf/util/time_util.h>
25
26using google::protobuf::util::TimeUtil;
27
28using namespace openshot;
29
30namespace {
31std::vector<uint32_t> encode_object_mask_rle(const std::vector<uint8_t>& mask)
32{
33 std::vector<uint32_t> rle;
34 uint8_t current = 0;
35 uint32_t count = 0;
36 for (uint8_t value : mask) {
37 value = value ? 1 : 0;
38 if (value == current) {
39 ++count;
40 } else {
41 rle.push_back(count);
42 current = value;
43 count = 1;
44 }
45 }
46 rle.push_back(count);
47 return rle;
48}
49
50std::vector<uint8_t> decode_object_mask_rle(const ObjectMaskData& mask)
51{
52 std::vector<uint8_t> decoded(static_cast<size_t>(mask.width * mask.height), 0);
53 int offset = 0;
54 bool value = false;
55 for (uint32_t count : mask.rle) {
56 const int end = std::min(mask.width * mask.height, offset + static_cast<int>(count));
57 if (value)
58 std::fill(decoded.begin() + offset, decoded.begin() + end, static_cast<uint8_t>(1));
59 offset = end;
60 value = !value;
61 if (offset >= mask.width * mask.height)
62 break;
63 }
64 return decoded;
65}
66
67ObjectMaskData transform_mask_between_boxes(
68 const ObjectMaskData& source_mask,
69 const BBox& source_box,
70 const BBox& target_box)
71{
72 ObjectMaskData result;
73 if (!source_mask.HasData() ||
74 source_box.width <= 0.0f || source_box.height <= 0.0f ||
75 target_box.width <= 0.0f || target_box.height <= 0.0f)
76 return result;
77
78 const float source_left = (source_box.cx - source_box.width / 2.0f) * source_mask.width;
79 const float source_top = (source_box.cy - source_box.height / 2.0f) * source_mask.height;
80 const float source_width = source_box.width * source_mask.width;
81 const float source_height = source_box.height * source_mask.height;
82 const float target_left = (target_box.cx - target_box.width / 2.0f) * source_mask.width;
83 const float target_top = (target_box.cy - target_box.height / 2.0f) * source_mask.height;
84 const float target_width = target_box.width * source_mask.width;
85 const float target_height = target_box.height * source_mask.height;
86 if (source_width <= 0.0f || source_height <= 0.0f || target_width <= 0.0f || target_height <= 0.0f)
87 return result;
88
89 const std::vector<uint8_t> source = decode_object_mask_rle(source_mask);
90 std::vector<uint8_t> transformed(static_cast<size_t>(source_mask.width * source_mask.height), 0);
91 const int min_x = std::max(0, static_cast<int>(std::floor(target_left)));
92 const int min_y = std::max(0, static_cast<int>(std::floor(target_top)));
93 const int max_x = std::min(source_mask.width, static_cast<int>(std::ceil(target_left + target_width)));
94 const int max_y = std::min(source_mask.height, static_cast<int>(std::ceil(target_top + target_height)));
95 for (int y = min_y; y < max_y; ++y) {
96 for (int x = min_x; x < max_x; ++x) {
97 const float source_x = source_left + (static_cast<float>(x) - target_left) * source_width / target_width;
98 const float source_y = source_top + (static_cast<float>(y) - target_top) * source_height / target_height;
99 const int sx = static_cast<int>(std::round(source_x));
100 const int sy = static_cast<int>(std::round(source_y));
101 if (sx < 0 || sx >= source_mask.width || sy < 0 || sy >= source_mask.height)
102 continue;
103 if (source[static_cast<size_t>(sy * source_mask.width + sx)])
104 transformed[static_cast<size_t>(y * source_mask.width + x)] = 1;
105 }
106 }
107
108 if (std::none_of(transformed.begin(), transformed.end(), [](uint8_t value) { return value != 0; }))
109 return result;
110 result.width = source_mask.width;
111 result.height = source_mask.height;
112 result.rle = encode_object_mask_rle(transformed);
113 return result;
114}
115}
116
117// Default Constructor, delegating
120
121// Constructor that takes RGBA values for stroke, and sets the bounding-box
122// displacement as 0 and the scales as 1 for the first frame
123TrackedObjectBBox::TrackedObjectBBox(int Red, int Green, int Blue, int Alfa)
124 : delta_x(0.0), delta_y(0.0),
125 scale_x(1.0), scale_y(1.0),
126 background_alpha(0.0), background_corner(12),
127 mask_alpha(120.0 / 255.0),
128 stroke_width(2) , stroke_alpha(0.7),
129 stroke(Red, Green, Blue, Alfa),
130 background(Red, Green, Blue, Alfa),
131 mask_color(Red, Green, Blue, Alfa)
132{
133 this->TimeScale = 1.0;
134}
135
136// Add a BBox to the BoxVec map
137void TrackedObjectBBox::AddBox(int64_t _frame_num, float _cx, float _cy, float _width, float _height, float _angle)
138{
139 // Check if the given frame number is valid
140 if (_frame_num < 0)
141 return;
142
143 // Instantiate a new bounding-box
144 BBox newBBox = BBox(_cx, _cy, _width, _height, _angle);
145
146 // Get the time of given frame
147 double time = this->FrameNToTime(_frame_num, 1.0);
148 // Create an iterator that points to the BoxVec pair indexed by the time of given frame
149 auto BBoxIterator = BoxVec.find(time);
150
151 if (BBoxIterator != BoxVec.end())
152 {
153 // There is a bounding-box indexed by the time of given frame, update-it
154 BBoxIterator->second = newBBox;
155 }
156 else
157 {
158 // There isn't a bounding-box indexed by the time of given frame, insert a new one
159 BoxVec.insert({time, newBBox});
160 }
161}
162
163void TrackedObjectBBox::AddMask(int64_t frame_num, const ObjectMaskData& mask)
164{
165 if (frame_num < 0 || !mask.HasData())
166 return;
167
168 double time = FrameNToTime(frame_num, 1.0);
169 MaskVec[time] = mask;
170}
171
172bool TrackedObjectBBox::HasMask(int64_t frame_num, int64_t max_frame_gap) const
173{
174 return GetMask(frame_num, max_frame_gap).HasData();
175}
176
178{
179 return !MaskVec.empty();
180}
181
182ObjectMaskData TrackedObjectBBox::GetMask(int64_t frame_num, int64_t max_frame_gap) const
183{
184 double time = FrameNToTime(frame_num, 1.0);
185 auto it = MaskVec.find(time);
186 if (it != MaskVec.end())
187 return it->second;
188 if (max_frame_gap <= 0 || MaskVec.empty())
189 return {};
190
191 auto after = MaskVec.lower_bound(time);
192 if (after == MaskVec.begin())
193 return {};
194
195 auto before = std::prev(after);
196 double max_gap_time = FrameNToTime(max_frame_gap, 1.0) - FrameNToTime(0, 1.0);
197 if (time - before->first <= max_gap_time + 0.000001) {
198 auto source_box = BoxVec.find(before->first);
199 auto target_box = BoxVec.find(time);
200 if (source_box != BoxVec.end() && target_box != BoxVec.end())
201 return transform_mask_between_boxes(before->second, source_box->second, target_box->second);
202 return before->second;
203 }
204 return {};
205}
206
207// Get the size of BoxVec map
209{
210 if (BoxVec.empty())
211 return 0;
212 if (BoxVec.size() == 1)
213 return 1;
214 return BoxVec.size();
215}
216
217// Check if there is a bounding-box in the given frame
218bool TrackedObjectBBox::Contains(int64_t frame_num) const
219{
220 // Get the time of given frame
221 double time = this->FrameNToTime(frame_num, 1.0);
222 // Create an iterator that points to the BoxVec pair indexed by the time of given frame (or the closest time)
223 auto it = BoxVec.lower_bound(time);
224 if (it == BoxVec.end()){
225 // BoxVec pair not found
226 return false;
227 }
228 return true;
229}
230
231// Check if there is a bounding-box in the exact frame number
232bool TrackedObjectBBox::ExactlyContains(int64_t frame_number) const
233{
234 // Get the time of given frame
235 double time = FrameNToTime(frame_number, 1.0);
236 // Create an iterator that points to the BoxVec pair indexed by the exact time of given frame
237 auto it = BoxVec.find(time);
238 if (it == BoxVec.end()){
239 // BoxVec pair not found
240 return false;
241 }
242 return true;
243}
244
245// Remove a bounding-box from the BoxVec map
246void TrackedObjectBBox::RemoveBox(int64_t frame_number)
247{
248 // Get the time of given frame
249 double time = this->FrameNToTime(frame_number, 1.0);
250 // Create an iterator that points to the BoxVec pair indexed by the time of given frame
251 auto it = BoxVec.find(time);
252 if (it != BoxVec.end())
253 {
254 // The BoxVec pair exists, so remove it
255 BoxVec.erase(time);
256 }
257 return;
258}
259
260// Return a bounding-box from BoxVec with it's properties adjusted by the Keyframes
261BBox TrackedObjectBBox::GetBox(int64_t frame_number)
262{
263 // Get the time position of the given frame.
264 double time = this->FrameNToTime(frame_number, this->TimeScale);
265
266 // Return a iterator pointing to the BoxVec pair indexed by time or to the pair indexed
267 // by the closest upper time value.
268 auto currentBBoxIterator = BoxVec.lower_bound(time);
269
270 // Check if there is a pair indexed by time, returns an empty bbox if there isn't.
271 if (currentBBoxIterator == BoxVec.end())
272 {
273 // Create and return an empty bounding-box object
274 BBox emptyBBox;
275 return emptyBBox;
276 }
277
278 // Check if the iterator matches a BBox indexed by time or points to the first element of BoxVec
279 if ((currentBBoxIterator->first == time) || (currentBBoxIterator == BoxVec.begin()))
280 {
281 // Get the BBox indexed by time
282 BBox currentBBox = currentBBoxIterator->second;
283
284 // Adjust the BBox properties by the Keyframes values
285 currentBBox.cx += this->delta_x.GetValue(frame_number);
286 currentBBox.cy += this->delta_y.GetValue(frame_number);
287 currentBBox.width *= this->scale_x.GetValue(frame_number);
288 currentBBox.height *= this->scale_y.GetValue(frame_number);
289 return currentBBox;
290 }
291
292 // BBox indexed by the closest upper time
293 BBox currentBBox = currentBBoxIterator->second;
294 // BBox indexed by the closet lower time
295 BBox previousBBox = prev(currentBBoxIterator, 1)->second;
296
297 // Interpolate a BBox in the middle of previousBBox and currentBBox
298 BBox interpolatedBBox = InterpolateBoxes(prev(currentBBoxIterator, 1)->first, currentBBoxIterator->first,
299 previousBBox, currentBBox, time);
300
301 // Adjust the BBox properties by the Keyframes values
302 interpolatedBBox.cx += this->delta_x.GetValue(frame_number);
303 interpolatedBBox.cy += this->delta_y.GetValue(frame_number);
304 interpolatedBBox.width *= this->scale_x.GetValue(frame_number);
305 interpolatedBBox.height *= this->scale_y.GetValue(frame_number);
306 return interpolatedBBox;
307}
308
309double TrackedObjectBBox::ScaledStrokeWidth(int64_t frame_number, int image_width, int image_height) const
310{
311 const double base_width = std::max(0.0, static_cast<double>(stroke_width.GetValue(frame_number)));
312 Clip* parent_clip = static_cast<Clip*>(ParentClip());
313 if (!parent_clip || image_width <= 0 || image_height <= 0)
314 return base_width;
315
316 int target_width = image_width;
317 int target_height = image_height;
318 if (parent_clip->ParentTimeline()) {
319 TimelineBase* timeline = static_cast<TimelineBase*>(parent_clip->ParentTimeline());
320 if (timeline->preview_width > 0)
321 target_width = timeline->preview_width;
322 if (timeline->preview_height > 0)
323 target_height = timeline->preview_height;
324 }
325
326 QSize output_size(image_width, image_height);
327 switch (parent_clip->scale) {
328 case SCALE_FIT:
329 output_size.scale(target_width, target_height, Qt::KeepAspectRatio);
330 break;
331 case SCALE_STRETCH:
332 output_size.scale(target_width, target_height, Qt::IgnoreAspectRatio);
333 break;
334 case SCALE_CROP:
335 output_size.scale(target_width, target_height, Qt::KeepAspectRatioByExpanding);
336 break;
337 case SCALE_NONE:
338 default:
339 break;
340 }
341 if (output_size.width() <= 0 || output_size.height() <= 0)
342 return base_width;
343
344 const double raster_scale_x = static_cast<double>(output_size.width()) / image_width;
345 const double raster_scale_y = static_cast<double>(output_size.height()) / image_height;
346 const double raster_scale = std::sqrt(raster_scale_x * raster_scale_y);
347 return base_width * std::max(raster_scale, 1.0 / std::max(raster_scale, 0.000001));
348}
349
350// Interpolate the bouding-boxes properties
351BBox TrackedObjectBBox::InterpolateBoxes(double t1, double t2, BBox left, BBox right, double target)
352{
353 // Interpolate the x-coordinate of the center point
355 Point cx_right(t2, right.cx, openshot::InterpolationType::LINEAR);
356 Point cx = InterpolateBetween(cx_left, cx_right, target, 0.01);
357
358 // Interpolate de y-coordinate of the center point
360 Point cy_right(t2, right.cy, openshot::InterpolationType::LINEAR);
361 Point cy = InterpolateBetween(cy_left, cy_right, target, 0.01);
362
363 // Interpolate the width
364 Point width_left(t1, left.width, openshot::InterpolationType::LINEAR);
365 Point width_right(t2, right.width, openshot::InterpolationType::LINEAR);
366 Point width = InterpolateBetween(width_left, width_right, target, 0.01);
367
368 // Interpolate the height
369 Point height_left(t1, left.height, openshot::InterpolationType::LINEAR);
370 Point height_right(t2, right.height, openshot::InterpolationType::LINEAR);
371 Point height = InterpolateBetween(height_left, height_right, target, 0.01);
372
373 // Interpolate the source bounding-box angle
374 Point angle_left(t1, left.angle, openshot::InterpolationType::LINEAR);
375 Point angle_right(t1, right.angle, openshot::InterpolationType::LINEAR);
376 Point angle = InterpolateBetween(angle_left, angle_right, target, 0.01);
377
378 // Create a bounding box with the interpolated points
379 BBox interpolatedBox(cx.co.Y, cy.co.Y, width.co.Y, height.co.Y, angle.co.Y);
380
381 return interpolatedBox;
382}
383
384// Update object's BaseFps
386 this->BaseFps = fps;
387 return;
388}
389
390// Return the object's BaseFps
392 return BaseFps;
393}
394
395// Get the time of the given frame
396double TrackedObjectBBox::FrameNToTime(int64_t frame_number, double time_scale) const{
397 double time = ((double)frame_number) * this->BaseFps.Reciprocal().ToDouble() * (1.0 / time_scale);
398
399 return time;
400}
401
402// Update the TimeScale member variable
403void TrackedObjectBBox::ScalePoints(double time_scale){
404 this->TimeScale = time_scale;
405}
406
407// Load the bounding-boxes information from the protobuf file
408bool TrackedObjectBBox::LoadBoxData(std::string inputFilePath)
409{
410 using std::ios;
411
412 // Variable to hold the loaded data
413 pb_tracker::Tracker bboxMessage;
414
415 // Read the existing tracker message.
416 std::fstream input(inputFilePath, ios::in | ios::binary);
417
418 // Check if it was able to read the protobuf data
419 if (!bboxMessage.ParseFromIstream(&input))
420 {
421 std::cerr << "Failed to parse protobuf message." << std::endl;
422 return false;
423 }
424
425 this->clear();
426
427 // Iterate over all frames of the saved message
428 for (size_t i = 0; i < bboxMessage.frame_size(); i++)
429 {
430 // Get data of the i-th frame
431 const pb_tracker::Frame &pbFrameData = bboxMessage.frame(i);
432
433 // Get frame number
434 size_t frame_number = pbFrameData.id();
435
436 // Get bounding box data from current frame
437 const pb_tracker::Frame::Box &box = pbFrameData.bounding_box();
438
439 float width = box.x2() - box.x1();
440 float height = box.y2() - box.y1();
441 float cx = box.x1() + width/2;
442 float cy = box.y1() + height/2;
443 float angle = 0.0;
444
445
446 if ( (cx >= 0.0) && (cy >= 0.0) && (width >= 0.0) && (height >= 0.0) )
447 {
448 // The bounding-box properties are valid, so add it to the BoxVec map
449 this->AddBox(frame_number, cx, cy, width, height, angle);
450 }
451 }
452
453 // Show the time stamp from the last update in tracker data file
454 if (bboxMessage.has_last_updated())
455 {
456 std::cout << " Loaded Data. Saved Time Stamp: "
457 << TimeUtil::ToString(bboxMessage.last_updated()) << std::endl;
458 }
459
460 // Delete all global objects allocated by libprotobuf.
461 google::protobuf::ShutdownProtobufLibrary();
462
463 return true;
464}
465
466// Clear the BoxVec map
468{
469 BoxVec.clear();
470 MaskVec.clear();
471}
472
473// Generate JSON string of this object
474std::string TrackedObjectBBox::Json() const
475{
476 // Return formatted string
477 return JsonValue().toStyledString();
478}
479
480// Generate Json::Value for this object
482{
483 // Create root json object
484 Json::Value root;
485
486 // Object's properties
487 root["box_id"] = Id();
488 root["BaseFPS"]["num"] = BaseFps.num;
489 root["BaseFPS"]["den"] = BaseFps.den;
490 root["TimeScale"] = TimeScale;
491
492 // Keyframe's properties
493 root["delta_x"] = delta_x.JsonValue();
494 root["delta_y"] = delta_y.JsonValue();
495 root["scale_x"] = scale_x.JsonValue();
496 root["scale_y"] = scale_y.JsonValue();
497 root["visible"] = visible.JsonValue();
498 root["draw_box"] = draw_box.JsonValue();
499 root["draw_text"] = draw_text.JsonValue();
500 if (!MaskVec.empty()) {
501 root["draw_mask"] = draw_mask.JsonValue();
502 root["mask_alpha"] = mask_alpha.JsonValue();
503 root["mask_color"] = mask_color.JsonValue();
504 }
505 root["stroke"] = stroke.JsonValue();
506 root["background_alpha"] = background_alpha.JsonValue();
507 root["background_corner"] = background_corner.JsonValue();
508 root["background"] = background.JsonValue();
509 root["stroke_width"] = stroke_width.JsonValue();
510 root["stroke_alpha"] = stroke_alpha.JsonValue();
511
512 // return JsonValue
513 return root;
514}
515
516// Load JSON string into this object
517void TrackedObjectBBox::SetJson(const std::string value)
518{
519 // Parse JSON string into JSON objects
520 try
521 {
522 const Json::Value root = openshot::stringToJson(value);
523 // Set all values that match
524 SetJsonValue(root);
525 }
526 catch (const std::exception &e)
527 {
528 // Error parsing JSON (or missing keys)
529 throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
530 }
531 return;
532}
533
534// Load Json::Value into this object
535void TrackedObjectBBox::SetJsonValue(const Json::Value root)
536{
537
538 // Set the Id by the given JSON object
539 if (!root["box_id"].isNull() && root["box_id"].asString() != "")
540 Id(root["box_id"].asString());
541
542 // Set the BaseFps by the given JSON object
543 if (!root["BaseFPS"].isNull() && root["BaseFPS"].isObject())
544 {
545 if (!root["BaseFPS"]["num"].isNull())
546 BaseFps.num = (int)root["BaseFPS"]["num"].asInt();
547 if (!root["BaseFPS"]["den"].isNull())
548 BaseFps.den = (int)root["BaseFPS"]["den"].asInt();
549 }
550 // Set the TimeScale by the given JSON object
551 if (!root["TimeScale"].isNull())
552 {
553 double scale = (double)root["TimeScale"].asDouble();
554 this->ScalePoints(scale);
555 }
556 // Set the protobuf data path by the given JSON object
557 if (!root["protobuf_data_path"].isNull())
558 protobufDataPath = root["protobuf_data_path"].asString();
559
560 // Set the Keyframes by the given JSON object
561 if (!root["delta_x"].isNull())
562 delta_x.SetJsonValue(root["delta_x"]);
563 if (!root["delta_y"].isNull())
564 delta_y.SetJsonValue(root["delta_y"]);
565 if (!root["scale_x"].isNull())
566 scale_x.SetJsonValue(root["scale_x"]);
567 if (!root["scale_y"].isNull())
568 scale_y.SetJsonValue(root["scale_y"]);
569 if (!root["visible"].isNull())
570 visible.SetJsonValue(root["visible"]);
571 if (!root["draw_box"].isNull())
572 draw_box.SetJsonValue(root["draw_box"]);
573 if (!root["draw_text"].isNull())
574 draw_text.SetJsonValue(root["draw_text"]);
575 if (!root["draw_mask"].isNull())
576 draw_mask.SetJsonValue(root["draw_mask"]);
577 if (!root["mask_alpha"].isNull())
578 mask_alpha.SetJsonValue(root["mask_alpha"]);
579 if (!root["mask_color"].isNull())
580 mask_color.SetJsonValue(root["mask_color"]);
581 if (!root["stroke"].isNull())
582 stroke.SetJsonValue(root["stroke"]);
583 if (!root["background_alpha"].isNull())
584 background_alpha.SetJsonValue(root["background_alpha"]);
585 if (!root["background_corner"].isNull())
586 background_corner.SetJsonValue(root["background_corner"]);
587 if (!root["background"].isNull())
588 background.SetJsonValue(root["background"]);
589 if (!root["stroke_width"].isNull())
590 stroke_width.SetJsonValue(root["stroke_width"]);
591 if (!root["stroke_alpha"].isNull())
592 stroke_alpha.SetJsonValue(root["stroke_alpha"]);
593 return;
594}
595
596// Get all properties for a specific frame (perfect for a UI to display the current state
597// of all properties at any time)
598Json::Value TrackedObjectBBox::PropertiesJSON(int64_t requested_frame) const
599{
600 Json::Value root;
601
602 BBox box = GetBox(requested_frame);
603
604 // Add the ID of this object to the JSON object
605 root["box_id"] = add_property_json("Box ID", 0.0, "string", Id(), NULL, -1, -1, true, requested_frame);
606
607 // Add the data of given frame bounding-box to the JSON object
608 root["x1"] = add_property_json("X1", box.cx-(box.width/2), "float", "", NULL, 0.0, 1.0, true, requested_frame);
609 root["y1"] = add_property_json("Y1", box.cy-(box.height/2), "float", "", NULL, 0.0, 1.0, true, requested_frame);
610 root["x2"] = add_property_json("X2", box.cx+(box.width/2), "float", "", NULL, 0.0, 1.0, true, requested_frame);
611 root["y2"] = add_property_json("Y2", box.cy+(box.height/2), "float", "", NULL, 0.0, 1.0, true, requested_frame);
612
613 // Add the bounding-box Keyframes to the JSON object
614 root["delta_x"] = add_property_json("Displacement X-axis", delta_x.GetValue(requested_frame), "float", "", &delta_x, -1.0, 1.0, false, requested_frame);
615 root["delta_y"] = add_property_json("Displacement Y-axis", delta_y.GetValue(requested_frame), "float", "", &delta_y, -1.0, 1.0, false, requested_frame);
616 root["scale_x"] = add_property_json("Scale (Width)", scale_x.GetValue(requested_frame), "float", "", &scale_x, 0.0, 1.0, false, requested_frame);
617 root["scale_y"] = add_property_json("Scale (Height)", scale_y.GetValue(requested_frame), "float", "", &scale_y, 0.0, 1.0, false, requested_frame);
618 root["visible"] = add_property_json("Visible", visible.GetValue(requested_frame), "int", "", &visible, 0, 1, true, requested_frame);
619
620 root["draw_box"] = add_property_json("Draw Box", draw_box.GetValue(requested_frame), "int", "", &draw_box, 0, 1, false, requested_frame);
621 root["draw_box"]["choices"].append(add_property_choice_json("Yes", true, draw_box.GetValue(requested_frame)));
622 root["draw_box"]["choices"].append(add_property_choice_json("No", false, draw_box.GetValue(requested_frame)));
623
624 root["draw_text"] = add_property_json("Draw Text", draw_text.GetValue(requested_frame), "int", "", &draw_text, 0, 1, false, requested_frame);
625 root["draw_text"]["choices"].append(add_property_choice_json("Yes", true, draw_text.GetValue(requested_frame)));
626 root["draw_text"]["choices"].append(add_property_choice_json("No", false, draw_text.GetValue(requested_frame)));
627
628 if (HasMaskData()) {
629 root["draw_mask"] = add_property_json("Draw Mask", draw_mask.GetValue(requested_frame), "int", "", &draw_mask, 0, 1, false, requested_frame);
630 root["draw_mask"]["choices"].append(add_property_choice_json("Yes", true, draw_mask.GetValue(requested_frame)));
631 root["draw_mask"]["choices"].append(add_property_choice_json("No", false, draw_mask.GetValue(requested_frame)));
632
633 root["mask_color"] = add_property_json("Mask Color", 0.0, "color", "", NULL, 0, 255, false, requested_frame);
634 root["mask_color"]["red"] = add_property_json("Red", mask_color.red.GetValue(requested_frame), "float", "", &mask_color.red, 0, 255, false, requested_frame);
635 root["mask_color"]["blue"] = add_property_json("Blue", mask_color.blue.GetValue(requested_frame), "float", "", &mask_color.blue, 0, 255, false, requested_frame);
636 root["mask_color"]["green"] = add_property_json("Green", mask_color.green.GetValue(requested_frame), "float", "", &mask_color.green, 0, 255, false, requested_frame);
637 root["mask_alpha"] = add_property_json("Mask Alpha", mask_alpha.GetValue(requested_frame), "float", "", &mask_alpha, 0.0, 1.0, false, requested_frame);
638 }
639
640 root["stroke"] = add_property_json("Border", 0.0, "color", "", NULL, 0, 255, false, requested_frame);
641 root["stroke"]["red"] = add_property_json("Red", stroke.red.GetValue(requested_frame), "float", "", &stroke.red, 0, 255, false, requested_frame);
642 root["stroke"]["blue"] = add_property_json("Blue", stroke.blue.GetValue(requested_frame), "float", "", &stroke.blue, 0, 255, false, requested_frame);
643 root["stroke"]["green"] = add_property_json("Green", stroke.green.GetValue(requested_frame), "float", "", &stroke.green, 0, 255, false, requested_frame);
644 root["stroke_width"] = add_property_json("Stroke Width", stroke_width.GetValue(requested_frame), "int", "", &stroke_width, 1, 10, false, requested_frame);
645 root["stroke_alpha"] = add_property_json("Stroke alpha", stroke_alpha.GetValue(requested_frame), "float", "", &stroke_alpha, 0.0, 1.0, false, requested_frame);
646
647 root["background_alpha"] = add_property_json("Background Alpha", background_alpha.GetValue(requested_frame), "float", "", &background_alpha, 0.0, 1.0, false, requested_frame);
648 root["background_corner"] = add_property_json("Background Corner Radius", background_corner.GetValue(requested_frame), "int", "", &background_corner, 0.0, 150.0, false, requested_frame);
649
650 root["background"] = add_property_json("Background", 0.0, "color", "", NULL, 0, 255, false, requested_frame);
651 root["background"]["red"] = add_property_json("Red", background.red.GetValue(requested_frame), "float", "", &background.red, 0, 255, false, requested_frame);
652 root["background"]["blue"] = add_property_json("Blue", background.blue.GetValue(requested_frame), "float", "", &background.blue, 0, 255, false, requested_frame);
653 root["background"]["green"] = add_property_json("Green", background.green.GetValue(requested_frame), "float", "", &background.green, 0, 255, false, requested_frame);
654
655 // Return formatted string
656 return root;
657}
658
659
660// Generate JSON for a property
661Json::Value TrackedObjectBBox::add_property_json(std::string name, float value, std::string type, std::string memo, const Keyframe* keyframe, float min_value, float max_value, bool readonly, int64_t requested_frame) const {
662
663 // Requested Point
664 const Point requested_point(requested_frame, requested_frame);
665
666 // Create JSON Object
667 Json::Value prop = Json::Value(Json::objectValue);
668 prop["name"] = name;
669 prop["value"] = value;
670 prop["memo"] = memo;
671 prop["type"] = type;
672 prop["min"] = min_value;
673 prop["max"] = max_value;
674 if (keyframe) {
675 prop["keyframe"] = keyframe->Contains(requested_point);
676 prop["points"] = int(keyframe->GetCount());
677 Point closest_point = keyframe->GetClosestPoint(requested_point);
678 prop["interpolation"] = closest_point.interpolation;
679 prop["closest_point_x"] = closest_point.co.X;
680 prop["previous_point_x"] = keyframe->GetPreviousPoint(closest_point).co.X;
681 }
682 else {
683 prop["keyframe"] = false;
684 prop["points"] = 0;
685 prop["interpolation"] = CONSTANT;
686 prop["closest_point_x"] = -1;
687 prop["previous_point_x"] = -1;
688 }
689
690 prop["readonly"] = readonly;
691 prop["choices"] = Json::Value(Json::arrayValue);
692
693 // return JsonValue
694 return prop;
695}
696
697// Return a map that contains the bounding box properties and it's keyframes indexed by their names
698std::map<std::string, float> TrackedObjectBBox::GetBoxValues(int64_t frame_number) const {
699
700 // Create the map
701 std::map<std::string, float> boxValues;
702
703 // Get bounding box of the current frame
704 BBox box = GetBox(frame_number);
705
706 // Save the bounding box properties
707 boxValues["cx"] = box.cx;
708 boxValues["cy"] = box.cy;
709 boxValues["w"] = box.width;
710 boxValues["h"] = box.height;
711 boxValues["ang"] = box.angle;
712
713 // Save the keyframes values
714 boxValues["sx"] = this->scale_x.GetValue(frame_number);
715 boxValues["sy"] = this->scale_y.GetValue(frame_number);
716 boxValues["dx"] = this->delta_x.GetValue(frame_number);
717 boxValues["dy"] = this->delta_y.GetValue(frame_number);
718 return boxValues;
719}
Header file for Clip class.
Header file for Timeline class.
Header file for the TrackedObjectBBox class.
This class represents a clip (used to arrange readers on the timeline)
Definition Clip.h:89
openshot::TimelineBase * ParentTimeline() override
Get the associated Timeline pointer (if any)
Definition Clip.h:310
openshot::ScaleType scale
Definition Clip.h:186
openshot::Keyframe blue
Curve representing the red value (0 - 255)
Definition Color.h:32
openshot::Keyframe red
Curve representing the red value (0 - 255)
Definition Color.h:30
openshot::Keyframe green
Curve representing the green value (0 - 255)
Definition Color.h:31
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition Color.cpp:118
Json::Value JsonValue() const
Generate Json::Value for this object.
Definition Color.cpp:87
double X
The X value of the coordinate (usually representing the frame #)
Definition Coordinate.h:40
double Y
The Y value of the coordinate (usually representing the value of the property being animated)
Definition Coordinate.h:41
This class represents a fraction.
Definition Fraction.h:30
int num
Numerator for the fraction.
Definition Fraction.h:32
double ToDouble() const
Return this fraction as a double (i.e. 1/2 = 0.5)
Definition Fraction.cpp:40
Fraction Reciprocal() const
Return the reciprocal as a Fraction.
Definition Fraction.cpp:78
int den
Denominator for the fraction.
Definition Fraction.h:33
Exception for invalid JSON.
Definition Exceptions.h:224
A Keyframe is a collection of Point instances, which is used to vary a number or property over time.
Definition KeyFrame.h:53
bool Contains(Point p) const
Does this keyframe contain a specific point.
Definition KeyFrame.cpp:184
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition KeyFrame.cpp:372
Point GetPreviousPoint(Point p) const
Get previous point (.
Definition KeyFrame.cpp:226
double GetValue(int64_t index) const
Get the value at a specific index.
Definition KeyFrame.cpp:258
Json::Value JsonValue() const
Generate Json::Value for this object.
Definition KeyFrame.cpp:339
int64_t GetCount() const
Get the number of points (i.e. # of points)
Definition KeyFrame.cpp:430
Point GetClosestPoint(Point p) const
Get current point (or closest point to the right) from the X coordinate (i.e. the frame number)
Definition KeyFrame.cpp:221
A Point is the basic building block of a key-frame curve.
Definition Point.h:64
Coordinate co
This is the primary coordinate.
Definition Point.h:66
InterpolationType interpolation
This is the interpolation mode.
Definition Point.h:69
This class represents a timeline (used for building generic timeline implementations)
int preview_height
Optional preview width of timeline image. If your preview window is smaller than the timeline,...
int preview_width
Optional preview width of timeline image. If your preview window is smaller than the timeline,...
This class contains the properties of a tracked object and functions to manipulate it.
Keyframe delta_x
X-direction displacement Keyframe.
Color mask_color
Object mask overlay color.
bool LoadBoxData(std::string inputFilePath)
Load the bounding-boxes information from the protobuf file.
Color stroke
Border line color.
int64_t GetLength() const
Get the size of BoxVec map.
Keyframe stroke_width
Thickness of border line.
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
std::map< double, ObjectMaskData > MaskVec
Index optional object masks by time of each frame.
void AddBox(int64_t _frame_num, float _cx, float _cy, float _width, float _height, float _angle) override
Add a BBox to the BoxVec map.
TrackedObjectBBox()
Default Constructor.
void SetBaseFPS(Fraction fps)
Update object's BaseFps.
void SetJson(const std::string value) override
Load JSON string into this object.
Json::Value add_property_json(std::string name, float value, std::string type, std::string memo, const Keyframe *keyframe, float min_value, float max_value, bool readonly, int64_t requested_frame) const
void clear()
Clear the BoxVec map.
Keyframe delta_y
Y-direction displacement Keyframe.
Keyframe mask_alpha
Object mask overlay opacity.
Color background
Background fill color.
double ScaledStrokeWidth(int64_t frame_number, int image_width, int image_height) const
Return stroke width adjusted for source-to-output raster scaling.
std::map< double, BBox > BoxVec
Index the bounding-box by time of each frame.
std::string Json() const override
Get and Set JSON methods.
bool ExactlyContains(int64_t frame_number) const override
Check if there is a bounding-box in the exact frame number.
void ScalePoints(double scale) override
Update the TimeScale member variable.
void AddMask(int64_t frame_num, const ObjectMaskData &mask)
BBox InterpolateBoxes(double t1, double t2, BBox left, BBox right, double target)
Interpolate the bouding-boxes properties.
Fraction GetBaseFPS()
Return the object's BaseFps.
Json::Value PropertiesJSON(int64_t requested_frame) const override
Keyframe background_alpha
Background box opacity.
void RemoveBox(int64_t frame_number)
Remove a bounding-box from the BoxVec map.
bool HasMask(int64_t frame_number, int64_t max_frame_gap=0) const
bool Contains(int64_t frame_number) const
Check if there is a bounding-box in the given frame.
std::map< std::string, float > GetBoxValues(int64_t frame_number) const override
Return a map that contains the bounding box properties and it's keyframes indexed by their names.
Json::Value JsonValue() const override
Generate Json::Value for this object.
Keyframe scale_y
Y-direction scale Keyframe.
std::string protobufDataPath
Path to the protobuf file that holds the bounding box points across the frames.
Keyframe stroke_alpha
Stroke box opacity.
double FrameNToTime(int64_t frame_number, double time_scale) const
Get the time of the given frame.
Keyframe scale_x
X-direction scale Keyframe.
BBox GetBox(int64_t frame_number)
Return a bounding-box from BoxVec with it's properties adjusted by the Keyframes.
ObjectMaskData GetMask(int64_t frame_number, int64_t max_frame_gap=0) const
Keyframe background_corner
Radius of rounded corners.
Keyframe draw_box
Keyframe to determine if a specific box is drawn (or hidden)
Json::Value add_property_choice_json(std::string name, int value, int selected_value) const
Generate JSON choice for a property (dropdown properties)
Keyframe visible
Keyframe to track if a box is visible in the current frame (read-only)
Keyframe draw_mask
Keyframe to determine if a specific object mask is drawn (when mask data exists)
Keyframe draw_text
Keyframe to determine if a specific object label is drawn (or hidden)
ClipBase * ParentClip() const
Get and set the parentClip of this object.
std::string Id() const
Get the id of this object.
This namespace is the default namespace for all code in the openshot library.
double InterpolateBetween(Point const &left, Point const &right, double target, double allowed_error)
Interpolate two points using the right Point's interpolation method.
Definition KeyFrame.cpp:80
@ SCALE_FIT
Scale the clip until either height or width fills the canvas (with no cropping)
Definition Enums.h:38
@ SCALE_STRETCH
Scale the clip until both height and width fill the canvas (distort to fit)
Definition Enums.h:39
@ SCALE_CROP
Scale the clip until both height and width fill the canvas (cropping the overlap)
Definition Enums.h:37
@ SCALE_NONE
Do not scale the clip.
Definition Enums.h:40
const Json::Value stringToJson(const std::string value)
Definition Json.cpp:16
@ CONSTANT
Constant curves jump from their previous position to a new one (with no interpolation).
Definition Point.h:31
@ LINEAR
Linear curves are angular, straight lines between two points.
Definition Point.h:30
This struct holds the information of a bounding-box.
float cy
y-coordinate of the bounding box center
float height
bounding box height
float cx
x-coordinate of the bounding box center
float width
bounding box width
float angle
bounding box rotation angle [degrees]