OpenShot Library | libopenshot  0.3.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 
22 #ifdef USE_IMAGEMAGICK
23  #include "ImageReader.h"
24 #endif
25 
26 using namespace openshot;
27 
29 Mask::Mask() : reader(NULL), replace_image(false), needs_refresh(true) {
30  // Init effect properties
31  init_effect_details();
32 }
33 
34 // Default constructor
35 Mask::Mask(ReaderBase *mask_reader, Keyframe mask_brightness, Keyframe mask_contrast) :
36  reader(mask_reader), brightness(mask_brightness), contrast(mask_contrast), replace_image(false), needs_refresh(true)
37 {
38  // Init effect properties
39  init_effect_details();
40 }
41 
42 // Init effect settings
43 void Mask::init_effect_details()
44 {
47 
49  info.class_name = "Mask";
50  info.name = "Alpha Mask / Wipe Transition";
51  info.description = "Uses a grayscale mask image to gradually wipe / transition between 2 images.";
52  info.has_audio = false;
53  info.has_video = true;
54 }
55 
56 // This method is required for all derived classes of EffectBase, and returns a
57 // modified openshot::Frame object
58 std::shared_ptr<openshot::Frame> Mask::GetFrame(std::shared_ptr<openshot::Frame> frame, int64_t frame_number) {
59  // Get the mask image (from the mask reader)
60  std::shared_ptr<QImage> frame_image = frame->GetImage();
61 
62  // Check if mask reader is open
63  #pragma omp critical (open_mask_reader)
64  {
65  if (reader && !reader->IsOpen())
66  reader->Open();
67  }
68 
69  // No reader (bail on applying the mask)
70  if (!reader)
71  return frame;
72 
73  // Get mask image (if missing or different size than frame image)
74  #pragma omp critical (open_mask_reader)
75  {
76  if (!original_mask || !reader->info.has_single_image || needs_refresh ||
77  (original_mask && original_mask->size() != frame_image->size())) {
78 
79  // Only get mask if needed
80  auto mask_without_sizing = std::make_shared<QImage>(
81  *reader->GetFrame(frame_number)->GetImage());
82 
83  // Resize mask image to match frame size
84  original_mask = std::make_shared<QImage>(
85  mask_without_sizing->scaled(
86  frame_image->width(), frame_image->height(),
87  Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
88  }
89  }
90 
91  // Refresh no longer needed
92  needs_refresh = false;
93 
94  // Get pixel arrays
95  unsigned char *pixels = (unsigned char *) frame_image->bits();
96  unsigned char *mask_pixels = (unsigned char *) original_mask->bits();
97 
98  double contrast_value = (contrast.GetValue(frame_number));
99  double brightness_value = (brightness.GetValue(frame_number));
100 
101  // Loop through mask pixels, and apply average gray value to frame alpha channel
102  for (int pixel = 0, byte_index=0; pixel < original_mask->width() * original_mask->height(); pixel++, byte_index+=4)
103  {
104  // Get the RGB values from the pixel
105  int R = mask_pixels[byte_index];
106  int G = mask_pixels[byte_index + 1];
107  int B = mask_pixels[byte_index + 2];
108  int A = mask_pixels[byte_index + 3];
109 
110  // Get the average luminosity
111  int gray_value = qGray(R, G, B);
112 
113  // Adjust the brightness
114  gray_value += (255 * brightness_value);
115 
116  // Adjust the contrast
117  float factor = (20 / std::fmax(0.00001, 20.0 - contrast_value));
118  gray_value = (factor * (gray_value - 128) + 128);
119 
120  // Calculate the % change in alpha
121  float alpha_percent = float(constrain(A - gray_value)) / 255.0;
122 
123  // Set the alpha channel to the gray value
124  if (replace_image) {
125  // Replace frame pixels with gray value (including alpha channel)
126  pixels[byte_index + 0] = gray_value;
127  pixels[byte_index + 1] = gray_value;
128  pixels[byte_index + 2] = gray_value;
129  pixels[byte_index + 3] = gray_value;
130  } else {
131  // Mulitply new alpha value with all the colors (since we are using a premultiplied
132  // alpha format)
133  pixels[byte_index + 0] *= alpha_percent;
134  pixels[byte_index + 1] *= alpha_percent;
135  pixels[byte_index + 2] *= alpha_percent;
136  pixels[byte_index + 3] *= alpha_percent;
137  }
138 
139  }
140 
141  // return the modified frame
142  return frame;
143 }
144 
145 // Generate JSON string of this object
146 std::string Mask::Json() const {
147 
148  // Return formatted string
149  return JsonValue().toStyledString();
150 }
151 
152 // Generate Json::Value for this object
153 Json::Value Mask::JsonValue() const {
154 
155  // Create root json object
156  Json::Value root = EffectBase::JsonValue(); // get parent properties
157  root["type"] = info.class_name;
158  root["brightness"] = brightness.JsonValue();
159  root["contrast"] = contrast.JsonValue();
160  if (reader)
161  root["reader"] = reader->JsonValue();
162  else
163  root["reader"] = Json::objectValue;
164  root["replace_image"] = replace_image;
165 
166  // return JsonValue
167  return root;
168 }
169 
170 // Load JSON string into this object
171 void Mask::SetJson(const std::string value) {
172 
173  // Parse JSON string into JSON objects
174  try
175  {
176  const Json::Value root = openshot::stringToJson(value);
177  // Set all values that match
178  SetJsonValue(root);
179  }
180  catch (const std::exception& e)
181  {
182  // Error parsing JSON (or missing keys)
183  throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
184  }
185 }
186 
187 // Load Json::Value into this object
188 void Mask::SetJsonValue(const Json::Value root) {
189 
190  // Set parent data
192 
193  // Set data from Json (if key is found)
194  if (!root["replace_image"].isNull())
195  replace_image = root["replace_image"].asBool();
196  if (!root["brightness"].isNull())
197  brightness.SetJsonValue(root["brightness"]);
198  if (!root["contrast"].isNull())
199  contrast.SetJsonValue(root["contrast"]);
200  if (!root["reader"].isNull()) // does Json contain a reader?
201  {
202  #pragma omp critical (open_mask_reader)
203  {
204  // This reader has changed, so refresh cached assets
205  needs_refresh = true;
206 
207  if (!root["reader"]["type"].isNull()) // does the reader Json contain a 'type'?
208  {
209  // Close previous reader (if any)
210  if (reader) {
211  // Close and delete existing reader (if any)
212  reader->Close();
213  delete reader;
214  reader = NULL;
215  }
216 
217  // Create new reader (and load properties)
218  std::string type = root["reader"]["type"].asString();
219 
220  if (type == "FFmpegReader") {
221 
222  // Create new reader
223  reader = new FFmpegReader(root["reader"]["path"].asString());
224  reader->SetJsonValue(root["reader"]);
225 
226  #ifdef USE_IMAGEMAGICK
227  } else if (type == "ImageReader") {
228 
229  // Create new reader
230  reader = new ImageReader(root["reader"]["path"].asString());
231  reader->SetJsonValue(root["reader"]);
232  #endif
233 
234  } else if (type == "QtImageReader") {
235 
236  // Create new reader
237  reader = new QtImageReader(root["reader"]["path"].asString());
238  reader->SetJsonValue(root["reader"]);
239 
240  } else if (type == "ChunkReader") {
241 
242  // Create new reader
243  reader = new ChunkReader(root["reader"]["path"].asString(), (ChunkVersion) root["reader"]["chunk_version"].asInt());
244  reader->SetJsonValue(root["reader"]);
245 
246  }
247  }
248 
249  }
250  }
251 
252 }
253 
254 // Get all properties for a specific frame
255 std::string Mask::PropertiesJSON(int64_t requested_frame) const {
256 
257  // Generate JSON properties list
258  Json::Value root;
259  root["id"] = add_property_json("ID", 0.0, "string", Id(), NULL, -1, -1, true, requested_frame);
260  root["position"] = add_property_json("Position", Position(), "float", "", NULL, 0, 30 * 60 * 60 * 48, false, requested_frame);
261  root["layer"] = add_property_json("Track", Layer(), "int", "", NULL, 0, 20, false, requested_frame);
262  root["start"] = add_property_json("Start", Start(), "float", "", NULL, 0, 30 * 60 * 60 * 48, false, requested_frame);
263  root["end"] = add_property_json("End", End(), "float", "", NULL, 0, 30 * 60 * 60 * 48, false, requested_frame);
264  root["duration"] = add_property_json("Duration", Duration(), "float", "", NULL, 0, 30 * 60 * 60 * 48, true, requested_frame);
265  root["replace_image"] = add_property_json("Replace Image", replace_image, "int", "", NULL, 0, 1, false, requested_frame);
266 
267  // Add replace_image choices (dropdown style)
268  root["replace_image"]["choices"].append(add_property_choice_json("Yes", true, replace_image));
269  root["replace_image"]["choices"].append(add_property_choice_json("No", false, replace_image));
270 
271  // Keyframes
272  root["brightness"] = add_property_json("Brightness", brightness.GetValue(requested_frame), "float", "", &brightness, -1.0, 1.0, false, requested_frame);
273  root["contrast"] = add_property_json("Contrast", contrast.GetValue(requested_frame), "float", "", &contrast, 0, 20, false, requested_frame);
274 
275  if (reader)
276  root["reader"] = add_property_json("Source", 0.0, "reader", reader->Json(), NULL, 0, 1, false, requested_frame);
277  else
278  root["reader"] = add_property_json("Source", 0.0, "reader", "{}", NULL, 0, 1, false, requested_frame);
279 
280  // Set the parent effect which properties this effect will inherit
281  root["parent_effect_id"] = add_property_json("Parent", 0.0, "string", info.parent_effect_id, NULL, -1, -1, false, requested_frame);
282 
283  // Return formatted string
284  return root.toStyledString();
285 }
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
float Start() const
Get start position (in seconds) of clip (trim start of video)
Definition: ClipBase.h:88
float Duration() const
Get the length of this clip (in seconds)
Definition: ClipBase.h:90
virtual float End() const
Get end position (in seconds) of clip (trim end of video)
Definition: ClipBase.h:89
std::string Id() const
Get the Id of this clip object.
Definition: ClipBase.h:85
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
int Layer() const
Get layer of clip on timeline (lower number is covered by higher numbers)
Definition: ClipBase.h:87
float Position() const
Get position on timeline (in seconds)
Definition: ClipBase.h:86
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:77
int constrain(int color_value)
Constrain a color value from 0 to 255.
Definition: EffectBase.cpp:58
virtual void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: EffectBase.cpp:112
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:113
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:54
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: KeyFrame.cpp:358
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:325
void SetJson(const std::string value) override
Load JSON string into this object.
Definition: Mask.cpp:171
Mask()
Blank constructor, useful when using Json to load the effect properties.
Definition: Mask.cpp:29
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:255
Json::Value JsonValue() const override
Generate Json::Value for this object.
Definition: Mask.cpp:153
std::string Json() const override
Generate JSON string of this object.
Definition: Mask.cpp:146
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
Definition: Mask.cpp:188
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:162
virtual Json::Value JsonValue() const =0
Generate Json::Value for this object.
Definition: ReaderBase.cpp:107
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
std::string parent_effect_id
Id of the parent effect (if there is one)
Definition: EffectBase.h:39
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