OpenShot Library | libopenshot  0.5.0
Mask.cpp
Go to the documentation of this file.
1 
9 // Copyright (c) 2008-2019 OpenShot Studios, LLC
10 //
11 // SPDX-License-Identifier: LGPL-3.0-or-later
12 
13 #include "Mask.h"
14 
15 #include "Exceptions.h"
16 
17 #include "ReaderBase.h"
18 #include "ChunkReader.h"
19 #include "FFmpegReader.h"
20 #include "QtImageReader.h"
21 #include <omp.h>
22 
23 #ifdef USE_IMAGEMAGICK
24  #include "ImageReader.h"
25 #endif
26 
27 using namespace openshot;
28 
30 Mask::Mask() : reader(NULL), replace_image(false), needs_refresh(true) {
31  // Init effect properties
32  init_effect_details();
33 }
34 
35 // Default constructor
36 Mask::Mask(ReaderBase *mask_reader, Keyframe mask_brightness, Keyframe mask_contrast) :
37  reader(mask_reader), brightness(mask_brightness), contrast(mask_contrast), replace_image(false), needs_refresh(true)
38 {
39  // Init effect properties
40  init_effect_details();
41 }
42 
43 // Init effect settings
44 void Mask::init_effect_details()
45 {
48 
50  info.class_name = "Mask";
51  info.name = "Alpha Mask / Wipe Transition";
52  info.description = "Uses a grayscale mask image to gradually wipe / transition between 2 images.";
53  info.has_audio = false;
54  info.has_video = true;
55 }
56 
57 // This method is required for all derived classes of EffectBase, and returns a
58 // modified openshot::Frame object
59 std::shared_ptr<openshot::Frame> Mask::GetFrame(std::shared_ptr<openshot::Frame> frame, int64_t frame_number) {
60  // Get the mask image (from the mask reader)
61  std::shared_ptr<QImage> frame_image = frame->GetImage();
62 
63  // Check if mask reader is open
64  #pragma omp critical (open_mask_reader)
65  {
66  if (reader && !reader->IsOpen())
67  reader->Open();
68  }
69 
70  // No reader (bail on applying the mask)
71  if (!reader)
72  return frame;
73 
74  // Get mask image (if missing or different size than frame image)
75  #pragma omp critical (open_mask_reader)
76  {
77  if (!original_mask || !reader->info.has_single_image || needs_refresh ||
78  (original_mask && original_mask->size() != frame_image->size())) {
79 
80  // Only get mask if needed
81  auto mask_without_sizing = std::make_shared<QImage>(
82  *reader->GetFrame(frame_number)->GetImage());
83 
84  // Resize mask image to match frame size
85  original_mask = std::make_shared<QImage>(
86  mask_without_sizing->scaled(
87  frame_image->width(), frame_image->height(),
88  Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
89  }
90  }
91 
92  // Once we've done the necessary resizing, we no longer need to refresh again
93  needs_refresh = false;
94 
95  // Grab raw pointers and dimensions one time
96  unsigned char* pixels = reinterpret_cast<unsigned char*>(frame_image->bits());
97  unsigned char* mask_pixels = reinterpret_cast<unsigned char*>(original_mask->bits());
98  int width = original_mask->width();
99  int height = original_mask->height();
100  int num_pixels = width * height; // total pixel count
101 
102  // Evaluate brightness and contrast keyframes just once
103  double contrast_value = contrast.GetValue(frame_number);
104  double brightness_value = brightness.GetValue(frame_number);
105 
106  int brightness_adj = static_cast<int>(255 * brightness_value);
107  float contrast_factor = 20.0f / std::max(0.00001f, 20.0f - static_cast<float>(contrast_value));
108 
109  // Iterate over every pixel in parallel
110 #pragma omp parallel for schedule(static)
111  for (int i = 0; i < num_pixels; ++i)
112  {
113  int idx = i * 4;
114 
115  int R = mask_pixels[idx + 0];
116  int G = mask_pixels[idx + 1];
117  int B = mask_pixels[idx + 2];
118  int A = mask_pixels[idx + 3];
119 
120  // Compute base gray, then apply brightness + contrast
121  int gray = qGray(R, G, B);
122  gray += brightness_adj;
123  gray = static_cast<int>(contrast_factor * (gray - 128) + 128);
124 
125  // Clamp (A - gray) into [0, 255]
126  int diff = A - gray;
127  if (diff < 0) diff = 0;
128  else if (diff > 255) diff = 255;
129 
130  // Calculate the % change in alpha
131  float alpha_percent = static_cast<float>(diff) / 255.0f;
132 
133  // Set the alpha channel to the gray value
134  if (replace_image) {
135  // Replace frame pixels with gray value (including alpha channel)
136  auto new_val = static_cast<unsigned char>(diff);
137  pixels[idx + 0] = new_val;
138  pixels[idx + 1] = new_val;
139  pixels[idx + 2] = new_val;
140  pixels[idx + 3] = new_val;
141  } else {
142  // Premultiplied RGBA → multiply each channel by alpha_percent
143  pixels[idx + 0] = static_cast<unsigned char>(pixels[idx + 0] * alpha_percent);
144  pixels[idx + 1] = static_cast<unsigned char>(pixels[idx + 1] * alpha_percent);
145  pixels[idx + 2] = static_cast<unsigned char>(pixels[idx + 2] * alpha_percent);
146  pixels[idx + 3] = static_cast<unsigned char>(pixels[idx + 3] * alpha_percent);
147  }
148 
149  }
150 
151  // return the modified frame
152  return frame;
153 }
154 
155 // Generate JSON string of this object
156 std::string Mask::Json() const {
157 
158  // Return formatted string
159  return JsonValue().toStyledString();
160 }
161 
162 // Generate Json::Value for this object
163 Json::Value Mask::JsonValue() const {
164 
165  // Create root json object
166  Json::Value root = EffectBase::JsonValue(); // get parent properties
167  root["type"] = info.class_name;
168  root["brightness"] = brightness.JsonValue();
169  root["contrast"] = contrast.JsonValue();
170  if (reader)
171  root["reader"] = reader->JsonValue();
172  else
173  root["reader"] = Json::objectValue;
174  root["replace_image"] = replace_image;
175 
176  // return JsonValue
177  return root;
178 }
179 
180 // Load JSON string into this object
181 void Mask::SetJson(const std::string value) {
182 
183  // Parse JSON string into JSON objects
184  try
185  {
186  const Json::Value root = openshot::stringToJson(value);
187  // Set all values that match
188  SetJsonValue(root);
189  }
190  catch (const std::exception& e)
191  {
192  // Error parsing JSON (or missing keys)
193  throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
194  }
195 }
196 
197 // Load Json::Value into this object
198 void Mask::SetJsonValue(const Json::Value root) {
199 
200  // Set parent data
202 
203  // Set data from Json (if key is found)
204  if (!root["replace_image"].isNull())
205  replace_image = root["replace_image"].asBool();
206  if (!root["brightness"].isNull())
207  brightness.SetJsonValue(root["brightness"]);
208  if (!root["contrast"].isNull())
209  contrast.SetJsonValue(root["contrast"]);
210  if (!root["reader"].isNull()) // does Json contain a reader?
211  {
212  #pragma omp critical (open_mask_reader)
213  {
214  // This reader has changed, so refresh cached assets
215  needs_refresh = true;
216 
217  if (!root["reader"]["type"].isNull()) // does the reader Json contain a 'type'?
218  {
219  // Close previous reader (if any)
220  if (reader) {
221  // Close and delete existing reader (if any)
222  reader->Close();
223  delete reader;
224  reader = NULL;
225  }
226 
227  // Create new reader (and load properties)
228  std::string type = root["reader"]["type"].asString();
229 
230  if (type == "FFmpegReader") {
231 
232  // Create new reader
233  reader = new FFmpegReader(root["reader"]["path"].asString());
234  reader->SetJsonValue(root["reader"]);
235 
236  #ifdef USE_IMAGEMAGICK
237  } else if (type == "ImageReader") {
238 
239  // Create new reader
240  reader = new ImageReader(root["reader"]["path"].asString());
241  reader->SetJsonValue(root["reader"]);
242  #endif
243 
244  } else if (type == "QtImageReader") {
245 
246  // Create new reader
247  reader = new QtImageReader(root["reader"]["path"].asString());
248  reader->SetJsonValue(root["reader"]);
249 
250  } else if (type == "ChunkReader") {
251 
252  // Create new reader
253  reader = new ChunkReader(root["reader"]["path"].asString(), (ChunkVersion) root["reader"]["chunk_version"].asInt());
254  reader->SetJsonValue(root["reader"]);
255 
256  }
257  }
258 
259  }
260  }
261 
262 }
263 
264 // Get all properties for a specific frame
265 std::string Mask::PropertiesJSON(int64_t requested_frame) const {
266 
267  // Generate JSON properties list
268  Json::Value root = BasePropertiesJSON(requested_frame);
269 
270  // Add replace_image choices (dropdown style)
271  root["replace_image"] = add_property_json("Replace Image", replace_image, "int", "", NULL, 0, 1, false, requested_frame);
272  root["replace_image"]["choices"].append(add_property_choice_json("Yes", true, replace_image));
273  root["replace_image"]["choices"].append(add_property_choice_json("No", false, replace_image));
274 
275  // Keyframes
276  root["brightness"] = add_property_json("Brightness", brightness.GetValue(requested_frame), "float", "", &brightness, -1.0, 1.0, false, requested_frame);
277  root["contrast"] = add_property_json("Contrast", contrast.GetValue(requested_frame), "float", "", &contrast, 0, 20, false, requested_frame);
278 
279  if (reader)
280  root["reader"] = add_property_json("Source", 0.0, "reader", reader->Json(), NULL, 0, 1, false, requested_frame);
281  else
282  root["reader"] = add_property_json("Source", 0.0, "reader", "{}", NULL, 0, 1, false, requested_frame);
283 
284  // Return formatted string
285  return root.toStyledString();
286 }
Header file for ChunkReader class.
Header file for all Exception classes.
Header file for FFmpegReader class.
Header file for ImageReader class.
Header file for Mask class.
Header file for QtImageReader class.
Header file for ReaderBase class.
This class reads a special chunk-formatted file, which can be easily shared in a distributed environm...
Definition: ChunkReader.h:79
Json::Value add_property_choice_json(std::string name, int value, int selected_value) const
Generate JSON choice for a property (dropdown properties)
Definition: ClipBase.cpp:132
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
Generate JSON for a property.
Definition: ClipBase.cpp:96
virtual Json::Value JsonValue() const
Generate Json::Value for this object.
Definition: EffectBase.cpp:79
Json::Value BasePropertiesJSON(int64_t requested_frame) const
Generate JSON object of base properties (recommended to be used by all effects)
Definition: EffectBase.cpp:179
virtual void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: EffectBase.cpp:115
EffectInfoStruct info
Information about the current effect.
Definition: EffectBase.h:69
This class uses the FFmpeg libraries, to open video files and audio files, and return openshot::Frame...
Definition: FFmpegReader.h:103
This class uses the ImageMagick++ libraries, to open image files, and return openshot::Frame objects ...
Definition: ImageReader.h:56
Exception for invalid JSON.
Definition: Exceptions.h:218
A Keyframe is a collection of Point instances, which is used to vary a number or property over time.
Definition: KeyFrame.h:53
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: KeyFrame.cpp:372
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
void SetJson(const std::string value) override
Load JSON string into this object.
Definition: Mask.cpp:181
Mask()
Blank constructor, useful when using Json to load the effect properties.
Definition: Mask.cpp:30
bool replace_image
Replace the frame image with a grayscale image representing the mask. Great for debugging a mask.
Definition: Mask.h:47
Keyframe brightness
Brightness keyframe to control the wipe / mask effect. A constant value here will prevent animation.
Definition: Mask.h:48
std::string PropertiesJSON(int64_t requested_frame) const override
Definition: Mask.cpp:265
Json::Value JsonValue() const override
Generate Json::Value for this object.
Definition: Mask.cpp:163
std::string Json() const override
Generate JSON string of this object.
Definition: Mask.cpp:156
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
Definition: Mask.cpp:198
Keyframe contrast
Contrast keyframe to control the hardness of the wipe effect / mask.
Definition: Mask.h:49
std::shared_ptr< openshot::Frame > GetFrame(int64_t frame_number) override
This method is required for all derived classes of ClipBase, and returns a new openshot::Frame object...
Definition: Mask.h:69
This class uses the Qt library, to open image files, and return openshot::Frame objects containing th...
Definition: QtImageReader.h:75
This abstract class is the base class, used by all readers in libopenshot.
Definition: ReaderBase.h:76
virtual bool IsOpen()=0
Determine if reader is open or closed.
openshot::ReaderInfo info
Information about the current media file.
Definition: ReaderBase.h:88
virtual std::string Json() const =0
Generate JSON string of this object.
virtual void SetJsonValue(const Json::Value root)=0
Load Json::Value into this object.
Definition: ReaderBase.cpp:157
virtual Json::Value JsonValue() const =0
Generate Json::Value for this object.
Definition: ReaderBase.cpp:106
virtual std::shared_ptr< openshot::Frame > GetFrame(int64_t number)=0
virtual void Open()=0
Open the reader (and start consuming resources, such as images or video files)
virtual void Close()=0
Close the reader (and any resources it was consuming)
This namespace is the default namespace for all code in the openshot library.
Definition: Compressor.h:29
ChunkVersion
This enumeration allows the user to choose which version of the chunk they would like (low,...
Definition: ChunkReader.h:50
const Json::Value stringToJson(const std::string value)
Definition: Json.cpp:16
bool has_video
Determines if this effect manipulates the image of a frame.
Definition: EffectBase.h:40
bool has_audio
Determines if this effect manipulates the audio of a frame.
Definition: EffectBase.h:41
std::string class_name
The class name of the effect.
Definition: EffectBase.h:36
std::string name
The name of the effect.
Definition: EffectBase.h:37
std::string description
The description of this effect and what it does.
Definition: EffectBase.h:38
bool has_single_image
Determines if this file only contains a single image.
Definition: ReaderBase.h:42