This method is required for all derived classes of EffectBase, and returns a modified openshot::Frame object
| 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); |