27 inline unsigned char clamp_u8(
int value) {
32 return static_cast<unsigned char>(value);
35 inline int clamp_i(
int value,
int min_value,
int max_value) {
36 if (value < min_value)
38 if (value > max_value)
43 inline float sample_bilinear(
const unsigned char* pixels,
int width,
int height,
float x,
float y,
int channel) {
44 x = std::fmax(0.0f, std::fmin(x,
static_cast<float>(width - 1)));
45 y = std::fmax(0.0f, std::fmin(y,
static_cast<float>(height - 1)));
47 const int x0 =
static_cast<int>(std::floor(x));
48 const int y0 =
static_cast<int>(std::floor(y));
49 const int x1 = clamp_i(x0 + 1, 0, width - 1);
50 const int y1 = clamp_i(y0 + 1, 0, height - 1);
51 const float tx = x - x0;
52 const float ty = y - y0;
54 const int idx00 = ((y0 * width) + x0) * 4 + channel;
55 const int idx10 = ((y0 * width) + x1) * 4 + channel;
56 const int idx01 = ((y1 * width) + x0) * 4 + channel;
57 const int idx11 = ((y1 * width) + x1) * 4 + channel;
59 const float top = (pixels[idx00] * (1.0f - tx)) + (pixels[idx10] * tx);
60 const float bottom = (pixels[idx01] * (1.0f - tx)) + (pixels[idx11] * tx);
61 return (top * (1.0f - ty)) + (bottom * ty);
67 : replace_image(false), invert(false), strength(1.0), horizontal(0.05), vertical(0.0), brightness(0.0), contrast(0.0) {
68 init_effect_details();
74 : replace_image(false), invert(false), strength(map_strength), horizontal(map_horizontal), vertical(map_vertical),
75 brightness(map_brightness), contrast(map_contrast) {
76 init_effect_details();
83 void Displace::init_effect_details()
89 info.
description =
"Use a grayscale image or video to warp the frame in the horizontal and vertical directions.";
94 std::shared_ptr<QImage> Displace::GetMapImage(std::shared_ptr<QImage> target_image, int64_t frame_number) {
95 if (!map_reader || !target_image || target_image->isNull())
98 std::shared_ptr<QImage> source_map;
99 bool used_cached_scaled =
false;
100 #pragma omp critical (open_effect_displace_reader)
104 if (!map_reader->
IsOpen())
108 cached_single_map_image &&
109 cached_single_map_width == target_image->width() &&
110 cached_single_map_height == target_image->height()) {
111 source_map = cached_single_map_image;
112 used_cached_scaled =
true;
115 int64_t mapped_frame = frame_number;
118 double host_fps = 30.0;
119 if (ParentTimeline()) {
125 if (host_fps > 0.0 && source_fps > 0.0) {
126 const int64_t requested_index = std::max(int64_t(0), frame_number - 1);
127 const double seconds =
static_cast<double>(requested_index) / host_fps;
128 mapped_frame =
static_cast<int64_t
>(std::floor(seconds * source_fps)) + 1;
130 mapped_frame = std::min(std::max(int64_t(1), mapped_frame), map_reader->
info.
video_length);
133 auto source_frame = map_reader->
GetFrame(mapped_frame);
134 if (source_frame && source_frame->GetImage() && !source_frame->GetImage()->isNull())
135 source_map = std::make_shared<QImage>(*source_frame->GetImage());
137 }
catch (
const std::exception& e) {
139 std::string(
"Displace::GetMapImage unable to read displacement frame: ") + e.what());
144 if (!source_map || source_map->isNull())
147 if (used_cached_scaled)
150 auto scaled_map = std::make_shared<QImage>(
152 target_image->width(), target_image->height(),
153 Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
155 cached_single_map_image = scaled_map;
156 cached_single_map_width = target_image->width();
157 cached_single_map_height = target_image->height();
164 std::shared_ptr<openshot::Frame>
Displace::GetFrame(std::shared_ptr<openshot::Frame> frame, int64_t frame_number) {
165 std::shared_ptr<QImage> frame_image = frame->GetImage();
166 if (!frame_image || frame_image->isNull())
169 auto map_image = GetMapImage(frame_image, frame_number);
170 if (!map_image || map_image->isNull())
173 const int width = frame_image->width();
174 const int height = frame_image->height();
175 const int pixel_count = width * height;
177 QImage original = frame_image->copy();
178 const unsigned char* original_pixels =
reinterpret_cast<const unsigned char*
>(original.constBits());
179 unsigned char* pixels =
reinterpret_cast<unsigned char*
>(frame_image->bits());
180 const unsigned char* map_pixels =
reinterpret_cast<const unsigned char*
>(map_image->constBits());
182 const float strength_value =
static_cast<float>(
strength.
GetValue(frame_number));
183 const float x_amount =
static_cast<float>(
horizontal.
GetValue(frame_number)) * width * strength_value;
184 const float y_amount =
static_cast<float>(
vertical.
GetValue(frame_number)) * height * strength_value;
185 const float contrast_value =
static_cast<float>(
contrast.
GetValue(frame_number));
186 const int brightness_adj =
static_cast<int>(255.0f *
brightness.
GetValue(frame_number));
187 const float contrast_factor = 20.0f / std::max(0.00001f, 20.0f - contrast_value);
190 std::array<unsigned char, 256> adjusted_gray{};
191 for (
int gray = 0; gray < 256; ++gray) {
192 const int adjusted =
static_cast<int>(contrast_factor * ((gray + brightness_adj) - 128) + 128);
193 adjusted_gray[gray] = clamp_u8(adjusted);
196 #pragma omp parallel for if(pixel_count >= 8192) schedule(static)
197 for (
int i = 0; i < pixel_count; ++i) {
198 const int idx = i * 4;
199 const int map_r = map_pixels[idx + 0];
200 const int map_g = map_pixels[idx + 1];
201 const int map_b = map_pixels[idx + 2];
202 const int map_a = map_pixels[idx + 3];
203 const int gray = ((map_r * 11) + (map_g * 16) + (map_b * 5)) >> 5;
204 int processed = adjusted_gray[gray];
206 processed = 255 - processed;
209 processed = 128 + (((processed - 128) * map_a) / 255);
212 const unsigned char debug_value = clamp_u8(processed);
213 pixels[idx + 0] = debug_value;
214 pixels[idx + 1] = debug_value;
215 pixels[idx + 2] = debug_value;
216 pixels[idx + 3] = 255;
220 const float signed_map = (
static_cast<float>(processed) - 127.5f) / 127.5f;
221 const int pixel_x = i % width;
222 const int pixel_y = i / width;
223 const float sample_x = pixel_x + (signed_map * x_amount);
224 const float sample_y = pixel_y + (signed_map * y_amount);
226 pixels[idx + 0] = clamp_u8(
static_cast<int>(std::lround(sample_bilinear(original_pixels, width, height, sample_x, sample_y, 0))));
227 pixels[idx + 1] = clamp_u8(
static_cast<int>(std::lround(sample_bilinear(original_pixels, width, height, sample_x, sample_y, 1))));
228 pixels[idx + 2] = clamp_u8(
static_cast<int>(std::lround(sample_bilinear(original_pixels, width, height, sample_x, sample_y, 2))));
229 pixels[idx + 3] = clamp_u8(
static_cast<int>(std::lround(sample_bilinear(original_pixels, width, height, sample_x, sample_y, 3))));
252 root[
"map_reader"] = map_reader->
JsonValue();
254 root[
"map_reader"] = Json::objectValue;
265 catch (
const std::exception& e)
267 throw InvalidJSON(
"JSON is invalid (missing keys or invalid data types)");
273 Json::Value normalized_root = root;
278 normalized_root.removeMember(
"reader");
279 normalized_root.removeMember(
"map_reader");
282 const Json::Value map_reader_json =
283 !root[
"map_reader"].isNull() ? root[
"map_reader"] : root[
"reader"];
284 if (!map_reader_json.isNull()) {
285 if (!map_reader_json[
"type"].isNull())
287 else if (map_reader_json.isObject() && map_reader_json.empty())
290 if (!root[
"replace_image"].isNull())
292 if (!root[
"invert"].isNull())
293 invert = root[
"invert"].asBool();
294 if (!root[
"strength"].isNull())
296 if (!root[
"horizontal"].isNull())
298 if (!root[
"vertical"].isNull())
300 if (!root[
"brightness"].isNull())
302 if (!root[
"contrast"].isNull())
317 root[
"map_reader"] =
add_property_json(
"Map: Source", 0.0,
"reader", map_reader->
Json(), NULL, 0, 1,
false, requested_frame);
319 root[
"map_reader"] =
add_property_json(
"Map: Source", 0.0,
"reader",
"{}", NULL, 0, 1,
false, requested_frame);
327 return root.toStyledString();
331 if (map_reader == new_reader)
339 map_reader = new_reader;
340 cached_single_map_image.reset();
341 cached_single_map_width = 0;
342 cached_single_map_height = 0;