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 <array>
19 #include <omp.h>
20 
21 using namespace openshot;
22 
24 Mask::Mask() : replace_image(false) {
25  // Init effect properties
26  init_effect_details();
27 }
28 
29 // Default constructor
30 Mask::Mask(ReaderBase *mask_reader, Keyframe mask_brightness, Keyframe mask_contrast) :
31  brightness(mask_brightness), contrast(mask_contrast), replace_image(false)
32 {
33  // Init effect properties
34  init_effect_details();
35 
36  // Keep ownership local by cloning externally-provided readers.
37  if (mask_reader)
38  MaskReader(CreateReaderFromJson(mask_reader->JsonValue()));
39 }
40 
41 // Init effect settings
42 void Mask::init_effect_details()
43 {
46 
48  info.class_name = "Mask";
49  info.name = "Alpha Mask / Wipe Transition";
50  info.description = "Uses a grayscale mask image to gradually wipe / transition between 2 images.";
51  info.has_audio = false;
52  info.has_video = true;
53 }
54 
55 // This method is required for all derived classes of EffectBase, and returns a
56 // modified openshot::Frame object
57 std::shared_ptr<openshot::Frame> Mask::GetFrame(std::shared_ptr<openshot::Frame> frame, int64_t frame_number) {
58  // Get frame image first
59  std::shared_ptr<QImage> frame_image = frame->GetImage();
60  if (!frame_image || frame_image->isNull())
61  return frame;
62 
63  // No reader (bail on applying the mask)
64  auto original_mask = ResolveMaskImage(frame_image, frame_number);
65  if (!original_mask || original_mask->isNull())
66  return frame;
67 
68  // Grab raw pointers and dimensions one time
69  unsigned char* pixels = reinterpret_cast<unsigned char*>(frame_image->bits());
70  unsigned char* mask_pixels = reinterpret_cast<unsigned char*>(original_mask->bits());
71  const int num_pixels = original_mask->width() * original_mask->height();
72 
73  // Evaluate brightness and contrast keyframes just once
74  double contrast_value = contrast.GetValue(frame_number);
75  double brightness_value = brightness.GetValue(frame_number);
76 
77  int brightness_adj = static_cast<int>(255 * brightness_value);
78  float contrast_factor = 20.0f / std::max(0.00001f, 20.0f - static_cast<float>(contrast_value));
79  const bool output_mask = replace_image;
80  const auto clamp_u8 = [](int value) -> unsigned char {
81  if (value < 0) return 0;
82  if (value > 255) return 255;
83  return static_cast<unsigned char>(value);
84  };
85  // Precompute gray->adjusted-gray mapping for this frame's brightness/contrast.
86  std::array<unsigned char, 256> adjusted_gray{};
87  for (int gray = 0; gray < 256; ++gray) {
88  const int adjusted = static_cast<int>(contrast_factor * ((gray + brightness_adj) - 128) + 128);
89  adjusted_gray[gray] = clamp_u8(adjusted);
90  }
91  // 8-bit multiply lookup for premultiplied alpha channel scaling.
92  static const std::array<std::array<unsigned char, 256>, 256> mul_lut = [] {
93  std::array<std::array<unsigned char, 256>, 256> lut{};
94  for (int alpha = 0; alpha < 256; ++alpha) {
95  for (int value = 0; value < 256; ++value) {
96  lut[alpha][value] = static_cast<unsigned char>((value * alpha) / 255);
97  }
98  }
99  return lut;
100  }();
101 
102  // Separate loops keep the hot path branch-free per pixel.
103  if (output_mask) {
104  #pragma omp parallel for if(num_pixels >= 16384) schedule(static)
105  for (int i = 0; i < num_pixels; ++i) {
106  const int idx = i * 4;
107  const int R = mask_pixels[idx + 0];
108  const int G = mask_pixels[idx + 1];
109  const int B = mask_pixels[idx + 2];
110  const int A = mask_pixels[idx + 3];
111 
112  const int gray = ((R * 11) + (G * 16) + (B * 5)) >> 5;
113  const int diff = A - adjusted_gray[gray];
114  const unsigned char new_val = clamp_u8(diff);
115  pixels[idx + 0] = new_val;
116  pixels[idx + 1] = new_val;
117  pixels[idx + 2] = new_val;
118  pixels[idx + 3] = new_val;
119  }
120  } else if (mask_invert) {
121  #pragma omp parallel for if(num_pixels >= 16384) schedule(static)
122  for (int i = 0; i < num_pixels; ++i) {
123  const int idx = i * 4;
124  const int R = mask_pixels[idx + 0];
125  const int G = mask_pixels[idx + 1];
126  const int B = mask_pixels[idx + 2];
127  const int A = mask_pixels[idx + 3];
128 
129  const int gray = ((R * 11) + (G * 16) + (B * 5)) >> 5;
130  int alpha = A - adjusted_gray[gray];
131  if (alpha < 0) alpha = 0;
132  else if (alpha > 255) alpha = 255;
133  alpha = 255 - alpha;
134 
135  // Premultiplied RGBA → multiply each channel by alpha
136  pixels[idx + 0] = mul_lut[alpha][pixels[idx + 0]];
137  pixels[idx + 1] = mul_lut[alpha][pixels[idx + 1]];
138  pixels[idx + 2] = mul_lut[alpha][pixels[idx + 2]];
139  pixels[idx + 3] = mul_lut[alpha][pixels[idx + 3]];
140  }
141  } else {
142  #pragma omp parallel for if(num_pixels >= 16384) schedule(static)
143  for (int i = 0; i < num_pixels; ++i) {
144  const int idx = i * 4;
145  const int R = mask_pixels[idx + 0];
146  const int G = mask_pixels[idx + 1];
147  const int B = mask_pixels[idx + 2];
148  const int A = mask_pixels[idx + 3];
149 
150  const int gray = ((R * 11) + (G * 16) + (B * 5)) >> 5;
151  int alpha = A - adjusted_gray[gray];
152  if (alpha < 0) alpha = 0;
153  else if (alpha > 255) alpha = 255;
154 
155  // Premultiplied RGBA → multiply each channel by alpha
156  pixels[idx + 0] = mul_lut[alpha][pixels[idx + 0]];
157  pixels[idx + 1] = mul_lut[alpha][pixels[idx + 1]];
158  pixels[idx + 2] = mul_lut[alpha][pixels[idx + 2]];
159  pixels[idx + 3] = mul_lut[alpha][pixels[idx + 3]];
160  }
161  }
162 
163  // return the modified frame
164  return frame;
165 }
166 
167 // Generate JSON string of this object
168 std::string Mask::Json() const {
169 
170  // Return formatted string
171  return JsonValue().toStyledString();
172 }
173 
174 // Generate Json::Value for this object
175 Json::Value Mask::JsonValue() const {
176 
177  // Create root json object
178  Json::Value root = EffectBase::JsonValue(); // get parent properties
179  root["type"] = info.class_name;
180  root["brightness"] = brightness.JsonValue();
181  root["contrast"] = contrast.JsonValue();
182  root["replace_image"] = replace_image;
183 
184  // return JsonValue
185  return root;
186 }
187 
188 // Load JSON string into this object
189 void Mask::SetJson(const std::string value) {
190 
191  // Parse JSON string into JSON objects
192  try
193  {
194  const Json::Value root = openshot::stringToJson(value);
195  // Set all values that match
196  SetJsonValue(root);
197  }
198  catch (const std::exception& e)
199  {
200  // Error parsing JSON (or missing keys)
201  throw InvalidJSON("JSON is invalid (missing keys or invalid data types)");
202  }
203 }
204 
205 // Load Json::Value into this object
206 void Mask::SetJsonValue(const Json::Value root) {
207  Json::Value normalized_root = root;
208  // Legacy compatibility: keep accepting "reader" on Mask effects.
209  if (!normalized_root["reader"].isNull() && normalized_root["mask_reader"].isNull())
210  normalized_root["mask_reader"] = normalized_root["reader"];
211 
212  // Set parent data
213  EffectBase::SetJsonValue(normalized_root);
214 
215  // Set data from Json (if key is found)
216  if (!normalized_root["replace_image"].isNull())
217  replace_image = normalized_root["replace_image"].asBool();
218  if (!normalized_root["brightness"].isNull())
219  brightness.SetJsonValue(normalized_root["brightness"]);
220  if (!normalized_root["contrast"].isNull())
221  contrast.SetJsonValue(normalized_root["contrast"]);
222 
223 }
224 
225 // Get all properties for a specific frame
226 std::string Mask::PropertiesJSON(int64_t requested_frame) const {
227 
228  // Generate JSON properties list
229  Json::Value root = BasePropertiesJSON(requested_frame);
230 
231  // Add replace_image choices (dropdown style)
232  root["replace_image"] = add_property_json("Replace Image", replace_image, "int", "", NULL, 0, 1, false, requested_frame);
233  root["replace_image"]["choices"].append(add_property_choice_json("Yes", true, replace_image));
234  root["replace_image"]["choices"].append(add_property_choice_json("No", false, replace_image));
235 
236  // Keyframes
237  root["brightness"] = add_property_json("Brightness", brightness.GetValue(requested_frame), "float", "", &brightness, -1.0, 1.0, false, requested_frame);
238  root["contrast"] = add_property_json("Contrast", contrast.GetValue(requested_frame), "float", "", &contrast, 0, 20, false, requested_frame);
239 
240  // Return formatted string
241  return root.toStyledString();
242 }
243 
244 void Mask::Reader(ReaderBase *new_reader) {
245  if (!new_reader) {
246  MaskReader(NULL);
247  return;
248  }
249 
250  // Keep ownership local by cloning externally-provided readers.
251  MaskReader(CreateReaderFromJson(new_reader->JsonValue()));
252 }
openshot::ClipBase::add_property_json
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
openshot::stringToJson
const Json::Value stringToJson(const std::string value)
Definition: Json.cpp:16
openshot::Mask::JsonValue
Json::Value JsonValue() const override
Generate Json::Value for this object.
Definition: Mask.cpp:175
openshot::EffectBase::info
EffectInfoStruct info
Information about the current effect.
Definition: EffectBase.h:110
openshot::ReaderBase::JsonValue
virtual Json::Value JsonValue() const =0
Generate Json::Value for this object.
Definition: ReaderBase.cpp:106
openshot::EffectBase::mask_invert
bool mask_invert
Invert grayscale mask values before blending.
Definition: EffectBase.h:111
openshot
This namespace is the default namespace for all code in the openshot library.
Definition: Compressor.h:28
openshot::Mask::GetFrame
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:68
openshot::ClipBase::add_property_choice_json
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
openshot::EffectBase::JsonValue
virtual Json::Value JsonValue() const
Generate Json::Value for this object.
Definition: EffectBase.cpp:96
openshot::Mask::contrast
Keyframe contrast
Contrast keyframe to control the hardness of the wipe effect / mask.
Definition: Mask.h:48
openshot::EffectBase::CreateReaderFromJson
ReaderBase * CreateReaderFromJson(const Json::Value &reader_json) const
Create a reader instance from reader JSON.
Definition: EffectBase.cpp:277
openshot::Keyframe::SetJsonValue
void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: KeyFrame.cpp:372
openshot::Mask::Mask
Mask()
Blank constructor, useful when using Json to load the effect properties.
Definition: Mask.cpp:24
openshot::Keyframe::JsonValue
Json::Value JsonValue() const
Generate Json::Value for this object.
Definition: KeyFrame.cpp:339
openshot::EffectBase::ResolveMaskImage
std::shared_ptr< QImage > ResolveMaskImage(std::shared_ptr< QImage > target_image, int64_t frame_number)
Resolve a cached/scaled mask image for the target frame dimensions.
Definition: EffectBase.h:88
openshot::Mask::PropertiesJSON
std::string PropertiesJSON(int64_t requested_frame) const override
Definition: Mask.cpp:226
openshot::EffectBase::BasePropertiesJSON
Json::Value BasePropertiesJSON(int64_t requested_frame) const
Generate JSON object of base properties (recommended to be used by all effects)
Definition: EffectBase.cpp:236
openshot::Keyframe
A Keyframe is a collection of Point instances, which is used to vary a number or property over time.
Definition: KeyFrame.h:53
openshot::InvalidJSON
Exception for invalid JSON.
Definition: Exceptions.h:223
openshot::EffectBase::InitEffectInfo
void InitEffectInfo()
Definition: EffectBase.cpp:37
openshot::EffectInfoStruct::has_audio
bool has_audio
Determines if this effect manipulates the audio of a frame.
Definition: EffectBase.h:44
openshot::EffectBase::MaskReader
ReaderBase * MaskReader()
Get the common mask reader.
Definition: EffectBase.h:175
Mask.h
Header file for Mask class.
openshot::Mask::replace_image
bool replace_image
Replace the frame image with a grayscale image representing the mask. Great for debugging a mask.
Definition: Mask.h:46
openshot::EffectInfoStruct::class_name
std::string class_name
The class name of the effect.
Definition: EffectBase.h:39
ReaderBase.h
Header file for ReaderBase class.
openshot::EffectInfoStruct::description
std::string description
The description of this effect and what it does.
Definition: EffectBase.h:41
openshot::EffectInfoStruct::has_video
bool has_video
Determines if this effect manipulates the image of a frame.
Definition: EffectBase.h:43
openshot::Mask::Json
std::string Json() const override
Generate JSON string of this object.
Definition: Mask.cpp:168
openshot::ReaderBase
This abstract class is the base class, used by all readers in libopenshot.
Definition: ReaderBase.h:75
openshot::EffectInfoStruct::name
std::string name
The name of the effect.
Definition: EffectBase.h:40
openshot::Mask::Reader
ReaderBase * Reader()
Get the reader object of the mask grayscale image.
Definition: Mask.h:92
openshot::Mask::SetJson
void SetJson(const std::string value) override
Load JSON string into this object.
Definition: Mask.cpp:189
openshot::Mask::brightness
Keyframe brightness
Brightness keyframe to control the wipe / mask effect. A constant value here will prevent animation.
Definition: Mask.h:47
openshot::Mask::SetJsonValue
void SetJsonValue(const Json::Value root) override
Load Json::Value into this object.
Definition: Mask.cpp:206
Exceptions.h
Header file for all Exception classes.
openshot::EffectBase::SetJsonValue
virtual void SetJsonValue(const Json::Value root)
Load Json::Value into this object.
Definition: EffectBase.cpp:139
openshot::Keyframe::GetValue
double GetValue(int64_t index) const
Get the value at a specific index.
Definition: KeyFrame.cpp:258