filters/combine.cpp
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // Avisynth v2.5. Copyright 2002 Ben Rudiak-Gould et al. | ||
| 2 | // http://avisynth.nl | ||
| 3 | |||
| 4 | // This program is free software; you can redistribute it and/or modify | ||
| 5 | // it under the terms of the GNU General Public License as published by | ||
| 6 | // the Free Software Foundation; either version 2 of the License, or | ||
| 7 | // (at your option) any later version. | ||
| 8 | // | ||
| 9 | // This program is distributed in the hope that it will be useful, | ||
| 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 12 | // GNU General Public License for more details. | ||
| 13 | // | ||
| 14 | // You should have received a copy of the GNU General Public License | ||
| 15 | // along with this program; if not, write to the Free Software | ||
| 16 | // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA, or visit | ||
| 17 | // http://www.gnu.org/copyleft/gpl.html . | ||
| 18 | // | ||
| 19 | // Linking Avisynth statically or dynamically with other modules is making a | ||
| 20 | // combined work based on Avisynth. Thus, the terms and conditions of the GNU | ||
| 21 | // General Public License cover the whole combination. | ||
| 22 | // | ||
| 23 | // As a special exception, the copyright holders of Avisynth give you | ||
| 24 | // permission to link Avisynth with independent modules that communicate with | ||
| 25 | // Avisynth solely through the interfaces defined in avisynth.h, regardless of the license | ||
| 26 | // terms of these independent modules, and to copy and distribute the | ||
| 27 | // resulting combined work under terms of your choice, provided that | ||
| 28 | // every copy of the combined work is accompanied by a complete copy of | ||
| 29 | // the source code of Avisynth (the version of Avisynth used to produce the | ||
| 30 | // combined work), being distributed under the terms of the GNU General | ||
| 31 | // Public License plus this exception. An independent module is a module | ||
| 32 | // which is not derived from or based on Avisynth, such as 3rd-party filters, | ||
| 33 | // import and export plugins, or graphical user interfaces. | ||
| 34 | |||
| 35 | #include "combine.h" | ||
| 36 | #include "../core/internal.h" | ||
| 37 | |||
| 38 | #ifdef AVS_WINDOWS | ||
| 39 | #include <avs/win.h> | ||
| 40 | #else | ||
| 41 | #include <avs/posix.h> | ||
| 42 | #endif | ||
| 43 | |||
| 44 | #include <avs/minmax.h> | ||
| 45 | #include <cmath> | ||
| 46 | #include <cassert> | ||
| 47 | #include <algorithm> | ||
| 48 | |||
| 49 | |||
| 50 | |||
| 51 | |||
| 52 | /******************************************************************** | ||
| 53 | ***** Declare index of new filters for Avisynth's filter engine ***** | ||
| 54 | ********************************************************************/ | ||
| 55 | |||
| 56 | extern const AVSFunction Combine_filters[] = { | ||
| 57 | { "StackVertical", BUILTIN_FUNC_PREFIX, "cc+", StackVertical::Create }, | ||
| 58 | { "StackHorizontal", BUILTIN_FUNC_PREFIX, "cc+", StackHorizontal::Create }, | ||
| 59 | { "MultiOverlay", BUILTIN_FUNC_PREFIX, "cc+i+", MultiOverlay::Create }, | ||
| 60 | { "ShowFiveVersions", BUILTIN_FUNC_PREFIX, "ccccc", ShowFiveVersions::Create }, | ||
| 61 | // first come the custom function versions, bacause of array_of_anything finish | ||
| 62 | { "Animate", BUILTIN_FUNC_PREFIX, "iisn.*", Animate::Create, (void*)1 }, // start frame, end frame, filter, function, start-args, end-args | ||
| 63 | { "Animate", BUILTIN_FUNC_PREFIX, "ciisn.*", Animate::Create, (void*)1 }, | ||
| 64 | // then the legacy linear version | ||
| 65 | { "Animate", BUILTIN_FUNC_PREFIX, "iis.*", Animate::Create, (void *)0 }, // start frame, end frame, filter, start-args, end-args | ||
| 66 | { "Animate", BUILTIN_FUNC_PREFIX, "ciis.*", Animate::Create, (void*)0 }, | ||
| 67 | { "ApplyRange", BUILTIN_FUNC_PREFIX, "ciis.*", Animate::Create_Range, (void*)0 }, | ||
| 68 | { 0 } | ||
| 69 | }; | ||
| 70 | |||
| 71 | |||
| 72 | |||
| 73 | |||
| 74 | |||
| 75 | |||
| 76 | /******************************** | ||
| 77 | ******* StackVertical ****** | ||
| 78 | ********************************/ | ||
| 79 | |||
| 80 | ✗ | StackVertical::StackVertical(const std::vector<PClip>& child_array, IScriptEnvironment* env) : | |
| 81 | ✗ | children(child_array) | |
| 82 | { | ||
| 83 | ✗ | vi = children[0]->GetVideoInfo(); | |
| 84 | |||
| 85 | ✗ | for (size_t i = 1; i < children.size(); ++i) { | |
| 86 | ✗ | const VideoInfo& vin = children[i]->GetVideoInfo(); | |
| 87 | |||
| 88 | ✗ | if (vi.width != vin.width) | |
| 89 | ✗ | env->ThrowError("StackVertical: image widths don't match"); | |
| 90 | |||
| 91 | ✗ | if (!vi.IsSameColorspace(vin)) | |
| 92 | ✗ | env->ThrowError("StackVertical: image formats don't match"); | |
| 93 | |||
| 94 | ✗ | if (vi.num_frames < vin.num_frames) // Max of all clips | |
| 95 | ✗ | vi.num_frames = vin.num_frames; | |
| 96 | |||
| 97 | ✗ | vi.height += vin.height; | |
| 98 | } | ||
| 99 | |||
| 100 | // reverse the order of the clips in RGB mode because it's upside-down | ||
| 101 | ✗ | if (vi.IsRGB() && !vi.IsPlanarRGB() && !vi.IsPlanarRGBA()) { | |
| 102 | ✗ | std::reverse(children.begin(), children.end()); | |
| 103 | // get audio and parity from the first in the original list | ||
| 104 | ✗ | firstchildindex = (int)children.size() - 1; | |
| 105 | } | ||
| 106 | else { | ||
| 107 | ✗ | firstchildindex = 0; | |
| 108 | } | ||
| 109 | ✗ | } | |
| 110 | |||
| 111 | |||
| 112 | ✗ | PVideoFrame __stdcall StackVertical::GetFrame(int n, IScriptEnvironment* env) | |
| 113 | { | ||
| 114 | ✗ | std::vector<PVideoFrame> frames; | |
| 115 | ✗ | frames.reserve(children.size()); | |
| 116 | |||
| 117 | ✗ | for (const auto& child: children) | |
| 118 | ✗ | frames.emplace_back(child->GetFrame(n, env)); | |
| 119 | |||
| 120 | ✗ | PVideoFrame dst = env->NewVideoFrameP(vi, &frames[0]); | |
| 121 | |||
| 122 | ✗ | const int dst_pitch = dst->GetPitch(); | |
| 123 | ✗ | const int row_size = dst->GetRowSize(); | |
| 124 | ✗ | BYTE* dstp = dst->GetWritePtr(); | |
| 125 | |||
| 126 | ✗ | for (const auto& src: frames) | |
| 127 | { | ||
| 128 | ✗ | const int src_height = src->GetHeight(); | |
| 129 | ✗ | env->BitBlt(dstp, dst_pitch, src->GetReadPtr(), src->GetPitch(), row_size, src_height); | |
| 130 | ✗ | dstp += dst_pitch * src_height; | |
| 131 | } | ||
| 132 | |||
| 133 | ✗ | if (vi.IsPlanar() && (vi.NumComponents() > 1)) | |
| 134 | { | ||
| 135 | // Copy Planar | ||
| 136 | ✗ | const int planesYUV[4] = { PLANAR_Y, PLANAR_U, PLANAR_V, PLANAR_A}; | |
| 137 | ✗ | const int planesRGB[4] = { PLANAR_G, PLANAR_B, PLANAR_R, PLANAR_A}; | |
| 138 | ✗ | const int *planes = vi.IsYUV() || vi.IsYUVA() ? planesYUV : planesRGB; | |
| 139 | |||
| 140 | // first plane is already processed | ||
| 141 | ✗ | for (int p = 1; p < vi.NumComponents(); p++) { | |
| 142 | ✗ | const int plane = planes[p]; | |
| 143 | ✗ | dstp = dst->GetWritePtr(plane); | |
| 144 | ✗ | const int dst_pitch = dst->GetPitch(plane); | |
| 145 | ✗ | const int row_size = dst->GetRowSize(plane); | |
| 146 | |||
| 147 | ✗ | for (const auto& src: frames) | |
| 148 | { | ||
| 149 | ✗ | const int src_height = src->GetHeight(plane); | |
| 150 | ✗ | env->BitBlt(dstp, dst_pitch, src->GetReadPtr(plane), src->GetPitch(plane), row_size, src_height); | |
| 151 | ✗ | dstp += dst_pitch * src_height; | |
| 152 | } | ||
| 153 | } | ||
| 154 | } | ||
| 155 | |||
| 156 | ✗ | return dst; | |
| 157 | ✗ | } | |
| 158 | |||
| 159 | ✗ | AVSValue __cdecl StackVertical::Create(AVSValue args, void*, IScriptEnvironment* env) | |
| 160 | { | ||
| 161 | ✗ | if (args[1].IsArray()) { | |
| 162 | ✗ | std::vector<PClip> children(1+args[1].ArraySize()); | |
| 163 | |||
| 164 | ✗ | children[0] = args[0].AsClip(); | |
| 165 | ✗ | for (int i = 1; i < (int)children.size(); ++i) // Copy clips | |
| 166 | ✗ | children[i] = args[1][i-1].AsClip(); | |
| 167 | |||
| 168 | ✗ | return new StackVertical(children, env); | |
| 169 | ✗ | } | |
| 170 | ✗ | else if (args[1].IsClip()) { // Make easy to call with trivial 2 clips | |
| 171 | ✗ | std::vector<PClip> children(2); | |
| 172 | |||
| 173 | ✗ | children[0] = args[0].AsClip(); | |
| 174 | ✗ | children[1] = args[1].AsClip(); | |
| 175 | |||
| 176 | ✗ | return new StackVertical(children, env); | |
| 177 | ✗ | } | |
| 178 | else { | ||
| 179 | ✗ | env->ThrowError("StackVertical: clip array not recognized!"); | |
| 180 | ✗ | return 0; | |
| 181 | } | ||
| 182 | } | ||
| 183 | |||
| 184 | /********************************** | ||
| 185 | ******* MultiOverlay ****** | ||
| 186 | **********************************/ | ||
| 187 | |||
| 188 | // Quick and dirty copy paste of N input clips onto the original (x,y) | ||
| 189 | // optionally specifying offsets in source and dimensions | ||
| 190 | ✗ | MultiOverlay::MultiOverlay(const std::vector<PClip>& child_array, const std::vector<int>& position_array, IScriptEnvironment* env) : | |
| 191 | ✗ | children(child_array), positions(position_array) | |
| 192 | { | ||
| 193 | ✗ | vi = children[0]->GetVideoInfo(); | |
| 194 | |||
| 195 | // 0th is the base clip | ||
| 196 | // 1.. the clips to overlay | ||
| 197 | ✗ | for (size_t i = 1; i < children.size(); ++i) { | |
| 198 | ✗ | const VideoInfo& vin = children[i]->GetVideoInfo(); | |
| 199 | ✗ | if (!vi.IsSameColorspace(vin)) | |
| 200 | ✗ | env->ThrowError("MultiOverlay: image format must match with the base"); | |
| 201 | } | ||
| 202 | |||
| 203 | ✗ | const size_t copy_paste_count = children.size() - 1; | |
| 204 | ✗ | const size_t param_count = position_array.size(); | |
| 205 | |||
| 206 | // either 2 values per clip | ||
| 207 | // target_x, target_y | ||
| 208 | // or 6 values per clip | ||
| 209 | // target_x, target_y, src_x, src_y, src_width, src_height | ||
| 210 | ✗ | if(copy_paste_count * 2 != param_count && copy_paste_count * 6 != param_count) | |
| 211 | ✗ | env->ThrowError("MultiOverlay: expected 2 or 6 values per source clip."); | |
| 212 | |||
| 213 | ✗ | const bool grey = vi.IsY(); | |
| 214 | ✗ | const bool isRGBPfamily = vi.IsPlanarRGB() || vi.IsPlanarRGBA(); | |
| 215 | |||
| 216 | ✗ | const int shift_w = (vi.IsPlanar() && !grey && !isRGBPfamily) ? vi.GetPlaneWidthSubsampling(PLANAR_U) : 0; | |
| 217 | ✗ | const int shift_h = (vi.IsPlanar() && !grey && !isRGBPfamily) ? vi.GetPlaneHeightSubsampling(PLANAR_U) : 0; | |
| 218 | ✗ | const int mask_w = (1 << shift_w) - 1; | |
| 219 | ✗ | const int mask_h = (1 << shift_h) - 1; | |
| 220 | |||
| 221 | ✗ | auto params_per_clip = param_count / copy_paste_count; | |
| 222 | ✗ | for (size_t i = 0; i < copy_paste_count; i++) { | |
| 223 | ✗ | const VideoInfo& vin = children[i + 1]->GetVideoInfo(); | |
| 224 | |||
| 225 | ✗ | const int target_x = positions[i * params_per_clip + 0]; | |
| 226 | ✗ | const int target_y = positions[i * params_per_clip + 1]; | |
| 227 | ✗ | if (target_x & mask_w) | |
| 228 | ✗ | env->ThrowError("MultiOverlay: target x must be mod %d for this video format", (1 << shift_w)); | |
| 229 | ✗ | if (target_y & mask_h) | |
| 230 | ✗ | env->ThrowError("MultiOverlay: target y must be mod %d for this video format", (1 << shift_h)); | |
| 231 | |||
| 232 | ✗ | if (params_per_clip == 6) { | |
| 233 | // additional source position and dimensions | ||
| 234 | ✗ | const int src_x = positions[i * params_per_clip + 2]; | |
| 235 | ✗ | const int src_y = positions[i * params_per_clip + 3]; | |
| 236 | ✗ | if (src_x < 0 || src_y < 0) | |
| 237 | ✗ | env->ThrowError("MultiOverlay: source coordinate cannot be negative."); | |
| 238 | |||
| 239 | ✗ | const int src_w = positions[i * params_per_clip + 4]; | |
| 240 | ✗ | const int src_h = positions[i * params_per_clip + 5]; | |
| 241 | ✗ | if (src_w <= 0 || src_h <= 0) | |
| 242 | ✗ | env->ThrowError("MultiOverlay: source width and height must be positive."); | |
| 243 | |||
| 244 | ✗ | if (src_x & mask_w) | |
| 245 | ✗ | env->ThrowError("MultiOverlay: source x must be mod %d for this video format", (1 << shift_w)); | |
| 246 | ✗ | if (src_y & mask_h) | |
| 247 | ✗ | env->ThrowError("MultiOverlay: source y must be mod %d for this video format", (1 << shift_h)); | |
| 248 | ✗ | if (src_w & mask_w) | |
| 249 | ✗ | env->ThrowError("MultiOverlay: source width must be mod %d for this video format", (1 << shift_w)); | |
| 250 | ✗ | if (src_h & mask_h) | |
| 251 | ✗ | env->ThrowError("MultiOverlay: source height must be mod %d for this video format", (1 << shift_h)); | |
| 252 | |||
| 253 | ✗ | if (src_x + src_w > vin.width) | |
| 254 | ✗ | env->ThrowError("MultiOverlay: copy exceeds clip width. x=%d, size=%d, clip width=%d", src_x, src_w, vin.width); | |
| 255 | ✗ | if (src_y + src_h > vin.height) | |
| 256 | ✗ | env->ThrowError("MultiOverlay: copy exceeds clip height. y=%d, size=%d, clip height=%d", src_y, src_h, vin.height); | |
| 257 | } | ||
| 258 | } | ||
| 259 | ✗ | } | |
| 260 | |||
| 261 | ✗ | PVideoFrame __stdcall MultiOverlay::GetFrame(int n, IScriptEnvironment* env) | |
| 262 | { | ||
| 263 | ✗ | std::vector<PVideoFrame> frames; | |
| 264 | ✗ | frames.reserve(children.size()); | |
| 265 | |||
| 266 | ✗ | for (const auto& child : children) | |
| 267 | ✗ | frames.emplace_back(child->GetFrame(n, env)); | |
| 268 | |||
| 269 | ✗ | PVideoFrame dst = frames[0]; // multioverlay target clip | |
| 270 | ✗ | env->MakeWritable(&dst); | |
| 271 | |||
| 272 | ✗ | const size_t copy_paste_count = children.size() - 1; | |
| 273 | ✗ | const size_t param_count = positions.size(); | |
| 274 | ✗ | const size_t params_per_clip = param_count / copy_paste_count; // 2 or 6 | |
| 275 | ✗ | const bool source_extra = params_per_clip == 6; | |
| 276 | |||
| 277 | // packed RGB is upside down | ||
| 278 | ✗ | const bool is_packed_rgb = vi.IsRGB24() || vi.IsRGB32() || vi.IsRGB48() || vi.IsRGB64(); | |
| 279 | ✗ | const int bfp = is_packed_rgb || vi.IsYUY2() ? vi.BytesFromPixels(1) : vi.ComponentSize(); | |
| 280 | // also for packed RGBs, pixelsize is not enough | ||
| 281 | |||
| 282 | ✗ | const int planesPacked[] = { DEFAULT_PLANE, DEFAULT_PLANE , DEFAULT_PLANE , DEFAULT_PLANE }; | |
| 283 | ✗ | const int planesYUV[4] = { PLANAR_Y, PLANAR_U, PLANAR_V, PLANAR_A }; | |
| 284 | ✗ | const int planesRGB[4] = { PLANAR_G, PLANAR_B, PLANAR_R, PLANAR_A }; | |
| 285 | ✗ | const int* planes = is_packed_rgb ? planesPacked : vi.IsY() || vi.IsYUV() || vi.IsYUVA() ? planesYUV : planesRGB; | |
| 286 | |||
| 287 | ✗ | const int planecount = is_packed_rgb || vi.IsYUY2() ? 1 : vi.NumComponents(); | |
| 288 | |||
| 289 | ✗ | for (int p = 0; p < planecount; p++) { | |
| 290 | ✗ | const int plane = planes[p]; | |
| 291 | ✗ | BYTE* dstp = dst->GetWritePtr(plane); | |
| 292 | ✗ | const int dst_pitch = dst->GetPitch(plane); | |
| 293 | ✗ | const int dst_height = dst->GetHeight(plane); | |
| 294 | |||
| 295 | // 0th was the target | ||
| 296 | ✗ | for (size_t i = 1; i < frames.size(); i++) | |
| 297 | { | ||
| 298 | ✗ | const auto& src = frames[i]; | |
| 299 | ✗ | const int src_rowsize = src->GetRowSize(plane); | |
| 300 | |||
| 301 | // these may be safely overridden | ||
| 302 | ✗ | int src_width = src_rowsize / bfp; | |
| 303 | ✗ | int src_height = src->GetHeight(plane); | |
| 304 | |||
| 305 | ✗ | const BYTE* srcp = src->GetReadPtr(plane); | |
| 306 | ✗ | const int src_pitch = src->GetPitch(plane); | |
| 307 | |||
| 308 | // packed RGB a Y and all 0th plane is not subsampled. | ||
| 309 | ✗ | const int shift_w = p == 0 ? 0 : vi.GetPlaneWidthSubsampling(plane); | |
| 310 | ✗ | const int shift_h = p == 0 ? 0 : vi.GetPlaneHeightSubsampling(plane); | |
| 311 | |||
| 312 | ✗ | int target_x = positions[(i - 1) * params_per_clip + 0] >> shift_w; | |
| 313 | ✗ | int target_y = positions[(i - 1) * params_per_clip + 1] >> shift_h; | |
| 314 | |||
| 315 | ✗ | int dst_w = dst->GetRowSize(plane) / bfp; | |
| 316 | |||
| 317 | ✗ | int src_x = 0; | |
| 318 | ✗ | int src_y = 0; | |
| 319 | ✗ | int src_w = src_width; | |
| 320 | ✗ | int src_h = src_height; | |
| 321 | |||
| 322 | ✗ | if (source_extra) { | |
| 323 | ✗ | src_x = positions[(i - 1) * params_per_clip + 2] >> shift_w;; | |
| 324 | ✗ | src_y = positions[(i - 1) * params_per_clip + 3] >> shift_h;; | |
| 325 | ✗ | src_w = positions[(i - 1) * params_per_clip + 4] >> shift_w;; | |
| 326 | ✗ | src_h = positions[(i - 1) * params_per_clip + 5] >> shift_h;; | |
| 327 | } | ||
| 328 | // bring negative target coordinates to 0, | ||
| 329 | // adjust source position and dimension accordingly | ||
| 330 | ✗ | if (target_x < 0) { | |
| 331 | ✗ | src_x += -target_x; | |
| 332 | ✗ | src_w -= -target_x; | |
| 333 | ✗ | target_x = 0; | |
| 334 | } | ||
| 335 | ✗ | if (target_y < 0) { | |
| 336 | ✗ | src_y += -target_y; | |
| 337 | ✗ | src_h -= -target_y; | |
| 338 | ✗ | target_y = 0; | |
| 339 | } | ||
| 340 | |||
| 341 | // check and limit dimension parameters | ||
| 342 | |||
| 343 | // Already checked at filter creation: | ||
| 344 | // o coordinates and dimensions are subsampling friendly | ||
| 345 | // o target x and y >= 0 | ||
| 346 | // o source width and height > 0 | ||
| 347 | // o src_x + src_width and src_y + src_height fit in source clip dimensions | ||
| 348 | |||
| 349 | ✗ | if (src_h > src_height) | |
| 350 | ✗ | src_h = src_height; | |
| 351 | ✗ | if (target_y + src_h > dst_height) | |
| 352 | ✗ | src_h -= (target_y + src_h - dst_height); | |
| 353 | |||
| 354 | ✗ | if (src_w > src_width) | |
| 355 | ✗ | src_w = src_width; | |
| 356 | ✗ | if (target_x + src_w > dst_w) | |
| 357 | ✗ | src_w -= (target_x + src_w - dst_w); | |
| 358 | |||
| 359 | ✗ | if (src_w > 0 && src_h > 0) { | |
| 360 | ✗ | src_height = src_h; | |
| 361 | ✗ | src_width = src_w * bfp; | |
| 362 | |||
| 363 | ✗ | if (is_packed_rgb) { | |
| 364 | // start from bottom: packed RGB is upside down | ||
| 365 | ✗ | src_y = (src->GetHeight(plane) - src_y - 1) - (src_height - 1); | |
| 366 | ✗ | target_y = (dst_height - target_y - 1) - (src_height - 1); | |
| 367 | } | ||
| 368 | |||
| 369 | ✗ | env->BitBlt( | |
| 370 | ✗ | dstp + target_x * bfp + target_y * dst_pitch, dst_pitch, | |
| 371 | ✗ | srcp + src_x * bfp + src_y * src_pitch, src_pitch, src_width, src_height); | |
| 372 | } | ||
| 373 | } | ||
| 374 | } | ||
| 375 | |||
| 376 | ✗ | return dst; | |
| 377 | ✗ | } | |
| 378 | |||
| 379 | ✗ | AVSValue __cdecl MultiOverlay::Create(AVSValue args, void*, IScriptEnvironment* env) | |
| 380 | { | ||
| 381 | ✗ | std::vector<PClip> children; | |
| 382 | ✗ | if (args[1].IsArray()) { | |
| 383 | ✗ | children.resize(1 + args[1].ArraySize()); | |
| 384 | |||
| 385 | ✗ | children[0] = args[0].AsClip(); | |
| 386 | ✗ | for (int i = 1; i < (int)children.size(); ++i) // Copy clips | |
| 387 | ✗ | children[i] = args[1][i - 1].AsClip(); | |
| 388 | } | ||
| 389 | ✗ | else if (args[1].IsClip()) { // Make easy to call with trivial 2 clips | |
| 390 | ✗ | children.resize(2); | |
| 391 | |||
| 392 | ✗ | children[0] = args[0].AsClip(); | |
| 393 | ✗ | children[1] = args[1].AsClip(); | |
| 394 | |||
| 395 | } | ||
| 396 | else { | ||
| 397 | ✗ | env->ThrowError("MultiOverlay: clip array not recognized!"); | |
| 398 | ✗ | return 0; | |
| 399 | } | ||
| 400 | |||
| 401 | ✗ | std::vector<int> positions; | |
| 402 | ✗ | if (!args[2].IsArray()) { | |
| 403 | ✗ | env->ThrowError("MultiOverlay: position array not recognized!"); | |
| 404 | ✗ | return 0; | |
| 405 | } | ||
| 406 | ✗ | const int pos_count = args[2].ArraySize(); | |
| 407 | ✗ | if (pos_count != 2 * (children.size() - 1) && pos_count != 6 * (children.size() - 1)) | |
| 408 | ✗ | env->ThrowError("MultiOverlay: position array must contain 2 or 6 entries for each clip to overlay!"); | |
| 409 | ✗ | positions.resize(pos_count); // x,y for each overlay clip | |
| 410 | |||
| 411 | ✗ | for (int i = 0; i < pos_count; ++i) // Copy positions, x, y, or x, y, src_x, src_y, src_width, src_height | |
| 412 | ✗ | positions[i] = args[2][i].AsInt(); | |
| 413 | |||
| 414 | ✗ | return new MultiOverlay(children, positions, env); | |
| 415 | |||
| 416 | ✗ | } | |
| 417 | |||
| 418 | |||
| 419 | /********************************** | ||
| 420 | ******* StackHorizontal ****** | ||
| 421 | **********************************/ | ||
| 422 | |||
| 423 | ✗ | StackHorizontal::StackHorizontal(const std::vector<PClip>& child_array, IScriptEnvironment* env) : | |
| 424 | ✗ | children(child_array) | |
| 425 | { | ||
| 426 | ✗ | vi = children[0]->GetVideoInfo(); | |
| 427 | |||
| 428 | ✗ | for (size_t i = 1; i < children.size(); ++i) { | |
| 429 | ✗ | const VideoInfo& vin = children[i]->GetVideoInfo(); | |
| 430 | |||
| 431 | ✗ | if (vi.height != vin.height) | |
| 432 | ✗ | env->ThrowError("StackHorizontal: image heights don't match"); | |
| 433 | |||
| 434 | ✗ | if (!vi.IsSameColorspace(vin)) | |
| 435 | ✗ | env->ThrowError("StackHorizontal: image formats don't match"); | |
| 436 | |||
| 437 | ✗ | if (vi.num_frames < vin.num_frames) // Max of all clips | |
| 438 | ✗ | vi.num_frames = vin.num_frames; | |
| 439 | |||
| 440 | ✗ | vi.width += vin.width; | |
| 441 | } | ||
| 442 | ✗ | } | |
| 443 | |||
| 444 | ✗ | PVideoFrame __stdcall StackHorizontal::GetFrame(int n, IScriptEnvironment* env) | |
| 445 | { | ||
| 446 | ✗ | std::vector<PVideoFrame> frames; | |
| 447 | ✗ | frames.reserve(children.size()); | |
| 448 | |||
| 449 | ✗ | for (const auto& child : children) | |
| 450 | ✗ | frames.emplace_back(child->GetFrame(n, env)); | |
| 451 | |||
| 452 | ✗ | PVideoFrame dst = env->NewVideoFrameP(vi, &frames[0]); | |
| 453 | ✗ | const int dst_pitch = dst->GetPitch(); | |
| 454 | ✗ | const int height = dst->GetHeight(); | |
| 455 | |||
| 456 | ✗ | BYTE* dstp = dst->GetWritePtr(); | |
| 457 | ✗ | for (const auto& src: frames) | |
| 458 | { | ||
| 459 | ✗ | const int src_rowsize = src->GetRowSize(); | |
| 460 | ✗ | env->BitBlt(dstp, dst_pitch, src->GetReadPtr(), src->GetPitch(), src_rowsize, height); | |
| 461 | ✗ | dstp += src_rowsize; | |
| 462 | } | ||
| 463 | |||
| 464 | ✗ | if (vi.IsPlanar() && (vi.NumComponents() > 1)) { | |
| 465 | // Copy Planar | ||
| 466 | |||
| 467 | ✗ | const int planesYUV[4] = { PLANAR_Y, PLANAR_U, PLANAR_V, PLANAR_A}; | |
| 468 | ✗ | const int planesRGB[4] = { PLANAR_G, PLANAR_B, PLANAR_R, PLANAR_A}; | |
| 469 | ✗ | const int *planes = vi.IsYUV() || vi.IsYUVA() ? planesYUV : planesRGB; | |
| 470 | |||
| 471 | // first plane is already processed | ||
| 472 | ✗ | for (int p = 1; p < vi.NumComponents(); p++) { | |
| 473 | ✗ | const int plane = planes[p]; | |
| 474 | ✗ | dstp = dst->GetWritePtr(plane); | |
| 475 | ✗ | const int dst_pitch = dst->GetPitch(plane); | |
| 476 | ✗ | const int height = dst->GetHeight(plane); | |
| 477 | |||
| 478 | ✗ | for (const auto& src: frames) | |
| 479 | { | ||
| 480 | ✗ | const int src_rowsize = src->GetRowSize(plane); | |
| 481 | ✗ | env->BitBlt(dstp, dst_pitch, src->GetReadPtr(plane), src->GetPitch(plane), src_rowsize, height); | |
| 482 | ✗ | dstp += src_rowsize; | |
| 483 | } | ||
| 484 | } | ||
| 485 | } | ||
| 486 | |||
| 487 | ✗ | return dst; | |
| 488 | ✗ | } | |
| 489 | |||
| 490 | ✗ | AVSValue __cdecl StackHorizontal::Create(AVSValue args, void*, IScriptEnvironment* env) | |
| 491 | { | ||
| 492 | ✗ | if (args[1].IsArray()) { | |
| 493 | ✗ | std::vector<PClip> children(1+args[1].ArraySize()); | |
| 494 | |||
| 495 | ✗ | children[0] = args[0].AsClip(); | |
| 496 | ✗ | for (int i = 1; i < (int)children.size(); ++i) // Copy clips | |
| 497 | ✗ | children[i] = args[1][i-1].AsClip(); | |
| 498 | |||
| 499 | ✗ | return new StackHorizontal(children, env); | |
| 500 | ✗ | } | |
| 501 | ✗ | else if (args[1].IsClip()) { // Make easy to call with trivial 2 clips | |
| 502 | ✗ | std::vector<PClip> children(2); | |
| 503 | |||
| 504 | ✗ | children[0] = args[0].AsClip(); | |
| 505 | ✗ | children[1] = args[1].AsClip(); | |
| 506 | |||
| 507 | ✗ | return new StackHorizontal(children, env); | |
| 508 | ✗ | } | |
| 509 | else { | ||
| 510 | ✗ | env->ThrowError("StackHorizontal: clip array not recognized!"); | |
| 511 | ✗ | return 0; | |
| 512 | } | ||
| 513 | } | ||
| 514 | |||
| 515 | |||
| 516 | |||
| 517 | /******************************** | ||
| 518 | ******* Five Versions ****** | ||
| 519 | ********************************/ | ||
| 520 | |||
| 521 |
3/10✓ Branch 4 → 5 taken 5 times.
✗ Branch 4 → 24 not taken.
✓ Branch 6 → 4 taken 5 times.
✓ Branch 6 → 7 taken 1 time.
✗ Branch 24 → 25 not taken.
✗ Branch 24 → 28 not taken.
✗ Branch 26 → 27 not taken.
✗ Branch 26 → 28 not taken.
✗ Branch 29 → 30 not taken.
✗ Branch 29 → 34 not taken.
|
6 | ShowFiveVersions::ShowFiveVersions(PClip* children, IScriptEnvironment* env) |
| 522 | { | ||
| 523 |
2/2✓ Branch 10 → 8 taken 5 times.
✓ Branch 10 → 11 taken 1 time.
|
6 | for (int b=0; b<5; ++b) |
| 524 |
1/2✓ Branch 8 → 9 taken 5 times.
✗ Branch 8 → 29 not taken.
|
5 | child[b] = children[b]; |
| 525 | |||
| 526 |
1/2✓ Branch 12 → 13 taken 1 time.
✗ Branch 12 → 29 not taken.
|
1 | vi = child[0]->GetVideoInfo(); |
| 527 | |||
| 528 |
2/2✓ Branch 22 → 14 taken 4 times.
✓ Branch 22 → 23 taken 1 time.
|
5 | for (int c=1; c<5; ++c) |
| 529 | { | ||
| 530 |
1/2✓ Branch 15 → 16 taken 4 times.
✗ Branch 15 → 29 not taken.
|
4 | const VideoInfo& viprime = child[c]->GetVideoInfo(); |
| 531 | 4 | vi.num_frames = max(vi.num_frames, viprime.num_frames); | |
| 532 |
3/6✓ Branch 17 → 18 taken 4 times.
✗ Branch 17 → 20 not taken.
✓ Branch 18 → 19 taken 4 times.
✗ Branch 18 → 20 not taken.
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 21 taken 4 times.
|
4 | if (vi.width != viprime.width || vi.height != viprime.height || vi.pixel_type != viprime.pixel_type) |
| 533 | ✗ | env->ThrowError("ShowFiveVersions: video attributes of all clips must match"); | |
| 534 | } | ||
| 535 | |||
| 536 | 1 | vi.width *= 3; | |
| 537 | 1 | vi.height *= 2; | |
| 538 |
0/2✗ Branch 31 → 32 not taken.
✗ Branch 31 → 34 not taken.
|
1 | } |
| 539 | |||
| 540 | 1 | PVideoFrame __stdcall ShowFiveVersions::GetFrame(int n, IScriptEnvironment* env) | |
| 541 | { | ||
| 542 | 1 | PVideoFrame dst = env->NewVideoFrame(vi); | |
| 543 | // frame property source is set later | ||
| 544 |
1/2✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 82 not taken.
|
1 | BYTE* dstp = dst->GetWritePtr(); |
| 545 |
1/2✓ Branch 6 → 7 taken 1 time.
✗ Branch 6 → 82 not taken.
|
1 | BYTE* dstpU = dst->GetWritePtr(PLANAR_U); |
| 546 |
1/2✓ Branch 8 → 9 taken 1 time.
✗ Branch 8 → 82 not taken.
|
1 | BYTE* dstpV = dst->GetWritePtr(PLANAR_V); |
| 547 |
1/2✓ Branch 10 → 11 taken 1 time.
✗ Branch 10 → 82 not taken.
|
1 | const int dst_pitch = dst->GetPitch(); |
| 548 |
1/2✓ Branch 12 → 13 taken 1 time.
✗ Branch 12 → 82 not taken.
|
1 | const int dst_pitchUV = dst->GetPitch(PLANAR_U); |
| 549 |
1/2✓ Branch 14 → 15 taken 1 time.
✗ Branch 14 → 82 not taken.
|
1 | const int height = dst->GetHeight()/2; |
| 550 |
1/2✓ Branch 16 → 17 taken 1 time.
✗ Branch 16 → 82 not taken.
|
1 | const int heightUV = dst->GetHeight(PLANAR_U)/2; |
| 551 | // todo: >8 bits, planar RGB | ||
| 552 |
2/4✓ Branch 17 → 18 taken 1 time.
✗ Branch 17 → 82 not taken.
✗ Branch 18 → 19 not taken.
✓ Branch 18 → 30 taken 1 time.
|
1 | if (vi.IsYUV()) { |
| 553 | ✗ | const int wg = dst->GetRowSize()/6; | |
| 554 | ✗ | for (int i=0; i<height; i++){ | |
| 555 | ✗ | memset(dstp + ((height+i)*dst_pitch), 128, wg); | |
| 556 | ✗ | memset(dstp + ((height+i)*dst_pitch) + wg*5, 128, wg); | |
| 557 | } | ||
| 558 | ✗ | if (dst_pitchUV) { | |
| 559 | ✗ | const int wgUV = dst->GetRowSize(PLANAR_U)/6; | |
| 560 | ✗ | for (int i=0; i<heightUV; i++) { | |
| 561 | ✗ | memset(dstpU + ((heightUV+i)*dst_pitchUV), 128, wgUV); | |
| 562 | ✗ | memset(dstpU + ((heightUV+i)*dst_pitchUV) + wgUV*5, 128, wgUV); | |
| 563 | ✗ | memset(dstpV + ((heightUV+i)*dst_pitchUV), 128, wgUV); | |
| 564 | ✗ | memset(dstpV + ((heightUV+i)*dst_pitchUV) + wgUV*5, 128, wgUV); | |
| 565 | } | ||
| 566 | } | ||
| 567 | } | ||
| 568 | else { // vi.IsRGB() | ||
| 569 |
1/2✓ Branch 31 → 32 taken 1 time.
✗ Branch 31 → 82 not taken.
|
1 | const int wg = dst->GetRowSize()/6; |
| 570 |
2/2✓ Branch 34 → 33 taken 4 times.
✓ Branch 34 → 35 taken 1 time.
|
5 | for (int i=0; i<height; i++){ |
| 571 | 4 | memset(dstp + i*dst_pitch, 128, wg); | |
| 572 | 4 | memset(dstp + i*dst_pitch + wg*5, 128, wg); | |
| 573 | } | ||
| 574 | } | ||
| 575 | |||
| 576 |
2/2✓ Branch 76 → 36 taken 5 times.
✓ Branch 76 → 77 taken 1 time.
|
6 | for (int c=0; c<5; ++c) |
| 577 | { | ||
| 578 |
1/2✓ Branch 37 → 38 taken 5 times.
✗ Branch 37 → 81 not taken.
|
5 | PVideoFrame src = child[c]->GetFrame(n, env); |
| 579 | |||
| 580 |
2/2✓ Branch 38 → 39 taken 1 time.
✓ Branch 38 → 40 taken 4 times.
|
5 | if(c == 0) // copy frame properties from the very first |
| 581 |
1/2✓ Branch 39 → 40 taken 1 time.
✗ Branch 39 → 79 not taken.
|
1 | env->copyFrameProps(src, dst); |
| 582 | |||
| 583 |
2/4✓ Branch 40 → 41 taken 5 times.
✗ Branch 40 → 79 not taken.
✓ Branch 41 → 42 taken 5 times.
✗ Branch 41 → 61 not taken.
|
5 | if (vi.IsPlanar()) { |
| 584 |
1/2✓ Branch 43 → 44 taken 5 times.
✗ Branch 43 → 79 not taken.
|
5 | const BYTE* srcpY = src->GetReadPtr(PLANAR_Y); |
| 585 |
1/2✓ Branch 45 → 46 taken 5 times.
✗ Branch 45 → 79 not taken.
|
5 | const BYTE* srcpU = src->GetReadPtr(PLANAR_U); |
| 586 |
1/2✓ Branch 47 → 48 taken 5 times.
✗ Branch 47 → 79 not taken.
|
5 | const BYTE* srcpV = src->GetReadPtr(PLANAR_V); |
| 587 |
1/2✓ Branch 49 → 50 taken 5 times.
✗ Branch 49 → 79 not taken.
|
5 | const int src_pitchY = src->GetPitch(PLANAR_Y); |
| 588 |
1/2✓ Branch 51 → 52 taken 5 times.
✗ Branch 51 → 79 not taken.
|
5 | const int src_pitchUV = src->GetPitch(PLANAR_U); |
| 589 |
1/2✓ Branch 53 → 54 taken 5 times.
✗ Branch 53 → 79 not taken.
|
5 | const int src_row_sizeY = src->GetRowSize(PLANAR_Y); |
| 590 |
1/2✓ Branch 55 → 56 taken 5 times.
✗ Branch 55 → 79 not taken.
|
5 | const int src_row_sizeUV = src->GetRowSize(PLANAR_U); |
| 591 | |||
| 592 | // staggered arrangement | ||
| 593 | 5 | BYTE* dstp2 = dstp + (c>>1) * src_row_sizeY; | |
| 594 | 5 | BYTE* dstp2U = dstpU + (c>>1) * src_row_sizeUV; | |
| 595 | 5 | BYTE* dstp2V = dstpV + (c>>1) * src_row_sizeUV; | |
| 596 |
2/2✓ Branch 56 → 57 taken 2 times.
✓ Branch 56 → 58 taken 3 times.
|
5 | if (c&1) { |
| 597 | 2 | dstp2 += (height * dst_pitch) + src_row_sizeY /2; | |
| 598 | 2 | dstp2U += (heightUV * dst_pitchUV) + src_row_sizeUV/2; | |
| 599 | 2 | dstp2V += (heightUV * dst_pitchUV) + src_row_sizeUV/2; | |
| 600 | } | ||
| 601 | |||
| 602 |
1/2✓ Branch 58 → 59 taken 5 times.
✗ Branch 58 → 79 not taken.
|
5 | env->BitBlt(dstp2, dst_pitch, srcpY, src_pitchY, src_row_sizeY, height); |
| 603 |
1/2✓ Branch 59 → 60 taken 5 times.
✗ Branch 59 → 79 not taken.
|
5 | env->BitBlt(dstp2U, dst_pitchUV, srcpU, src_pitchUV, src_row_sizeUV, heightUV); |
| 604 |
1/2✓ Branch 60 → 74 taken 5 times.
✗ Branch 60 → 79 not taken.
|
5 | env->BitBlt(dstp2V, dst_pitchUV, srcpV, src_pitchUV, src_row_sizeUV, heightUV); |
| 605 | } | ||
| 606 | else { | ||
| 607 | ✗ | const BYTE* srcp = src->GetReadPtr(); | |
| 608 | ✗ | const int src_pitch = src->GetPitch(); | |
| 609 | ✗ | const int src_row_size = src->GetRowSize(); | |
| 610 | |||
| 611 | // staggered arrangement | ||
| 612 | ✗ | BYTE* dstp2 = dstp + (c>>1) * src_row_size; | |
| 613 | ✗ | if ((c&1)^vi.IsRGB()) | |
| 614 | ✗ | dstp2 += (height * dst_pitch); | |
| 615 | ✗ | if (c&1) | |
| 616 | ✗ | dstp2 += vi.BytesFromPixels(vi.width/6); | |
| 617 | |||
| 618 | ✗ | env->BitBlt(dstp2, dst_pitch, srcp, src_pitch, src_row_size, height); | |
| 619 | } | ||
| 620 | 5 | } | |
| 621 | |||
| 622 | 1 | return dst; | |
| 623 | ✗ | } | |
| 624 | |||
| 625 | |||
| 626 | ✗ | AVSValue __cdecl ShowFiveVersions::Create(AVSValue args, void*, IScriptEnvironment* env) | |
| 627 | { | ||
| 628 | ✗ | PClip children[5]; | |
| 629 | ✗ | for (int i=0; i<5; ++i) | |
| 630 | ✗ | children[i] = args[i].AsClip(); | |
| 631 | ✗ | return new ShowFiveVersions(children, env); | |
| 632 | ✗ | } | |
| 633 | |||
| 634 | |||
| 635 | |||
| 636 | |||
| 637 | |||
| 638 | |||
| 639 | |||
| 640 | |||
| 641 | |||
| 642 | |||
| 643 | /************************************** | ||
| 644 | ******* Animate (Recursive) ****** | ||
| 645 | **************************************/ | ||
| 646 | |||
| 647 | ✗ | Animate::Animate( PClip context, int _first, int _last, const char* _name, const AVSValue* _args_before, | |
| 648 | ✗ | const AVSValue* _args_after, int _num_args, bool _range_limit, const AVSValue& _custom_fn, IScriptEnvironment* env ) | |
| 649 | ✗ | : first(_first), last(_last), num_args(_num_args), name(_name), range_limit(_range_limit), custom_fn(_custom_fn) | |
| 650 | { | ||
| 651 | ✗ | if (first > last) | |
| 652 | ✗ | env->ThrowError("Animate: final frame number must be greater than initial."); | |
| 653 | |||
| 654 | ✗ | if (first == last && (!range_limit)) | |
| 655 | ✗ | env->ThrowError("Animate: final frame cannot be the same as initial frame."); | |
| 656 | |||
| 657 | // check that argument types match | ||
| 658 | ✗ | for (int arg=0; arg<num_args; ++arg) { | |
| 659 | ✗ | const AVSValue& a = _args_before[arg]; | |
| 660 | ✗ | const AVSValue& b = _args_after[arg]; | |
| 661 | ✗ | if (a.IsString() && b.IsString()) { | |
| 662 | ✗ | if (lstrcmp(a.AsString(), b.AsString())) | |
| 663 | ✗ | env->ThrowError("Animate: string arguments must match before and after"); | |
| 664 | } | ||
| 665 | ✗ | else if (a.IsBool() && b.IsBool()) { | |
| 666 | ✗ | if (a.AsBool() != b.AsBool()) | |
| 667 | ✗ | env->ThrowError("Animate: boolean arguments must match before and after"); | |
| 668 | } | ||
| 669 | ✗ | else if (a.IsFloat() && b.IsFloat()) { | |
| 670 | // ok; also catches other numeric types | ||
| 671 | } | ||
| 672 | ✗ | else if (a.IsClip() && b.IsClip()) { | |
| 673 | // ok | ||
| 674 | } | ||
| 675 | else { | ||
| 676 | ✗ | env->ThrowError("Animate: must have two argument lists with matching types"); | |
| 677 | } | ||
| 678 | } | ||
| 679 | |||
| 680 | ✗ | if (custom_fn.IsFunction()) { | |
| 681 | // pre-check validity | ||
| 682 | |||
| 683 | // We could even check if function returns f(0.0) = 0.0 and f(1.0) = 1.0 | ||
| 684 | // But we are not that serious. Who implements custom function, will take care of it if needed | ||
| 685 | ✗ | constexpr bool check_first = false; | |
| 686 | ✗ | constexpr bool check_last = false; | |
| 687 | |||
| 688 | ✗ | double result_first = 0.0; | |
| 689 | ✗ | double result_last = 1.0; | |
| 690 | ✗ | PFunction func = custom_fn.AsFunction(); | |
| 691 | try { | ||
| 692 | |||
| 693 | // The Animate callback is a plain scalar function, no child | ||
| 694 | // one compulsory double "stage" parameter | ||
| 695 | ✗ | const char* argnames[1] = { "stage" }; | |
| 696 | |||
| 697 | // for syntax check this is always called | ||
| 698 | ✗ | AVSValue test1_args[1] = { 0.0 }; | |
| 699 | ✗ | const AVSValue test1_args_array = AVSValue(test1_args, 1); | |
| 700 | ✗ | result_first = env->Invoke3(AVSValue(), func, test1_args_array, argnames).AsFloat(); | |
| 701 | |||
| 702 | if (check_last) { | ||
| 703 | if (first != last) { | ||
| 704 | AVSValue test2_args[1] = { 1.0 }; | ||
| 705 | const AVSValue test2_args_array = AVSValue(test2_args, 1); | ||
| 706 | result_last = env->Invoke3(AVSValue(), func, test2_args_array, argnames).AsFloat(); | ||
| 707 | } | ||
| 708 | } | ||
| 709 | ✗ | } | |
| 710 | ✗ | catch (IScriptEnvironment::NotFound) { | |
| 711 | ✗ | env->ThrowError("Animate: Invalid function parameter type '%s'(%s)\n" | |
| 712 | "Function must have exactly one float argument: stage", | ||
| 713 | ✗ | func->GetDefinition()->param_types, func->ToString(env)); | |
| 714 | ✗ | } | |
| 715 | ✗ | catch (const AvisynthError& error) { | |
| 716 | ✗ | env->ThrowError("Animate: Error in custom function: %s\n%s", | |
| 717 | ✗ | func->ToString(env), error.msg); | |
| 718 | ✗ | } | |
| 719 | |||
| 720 | if (check_first && result_first != 0.0) | ||
| 721 | env->ThrowError("Animate: Error in custom function, for stage 0.0: 0.0 must be returned.\n%s\n", | ||
| 722 | func->ToString(env)); | ||
| 723 | if (check_last && result_last != 1.0) | ||
| 724 | env->ThrowError("Animate: Error in custom function, for stage 1.0: 1.0 must be returned.\n%s\n", | ||
| 725 | func->ToString(env)); | ||
| 726 | ✗ | } | |
| 727 | |||
| 728 | // copy args, and add initial clip arg for OOP notation | ||
| 729 | |||
| 730 | ✗ | if (context) | |
| 731 | ✗ | num_args++; | |
| 732 | |||
| 733 | ✗ | args_before = std::vector<AVSValue>(num_args*3); | |
| 734 | ✗ | args_after = args_before.data() + num_args; | |
| 735 | ✗ | args_now = args_after + num_args; | |
| 736 | |||
| 737 | ✗ | if (context) { | |
| 738 | ✗ | args_after[0] = args_before[0] = context; | |
| 739 | ✗ | for (int i=1; i<num_args; ++i) { | |
| 740 | ✗ | args_before[i] = _args_before[i-1]; | |
| 741 | ✗ | args_after[i] = _args_after[i-1]; | |
| 742 | } | ||
| 743 | } | ||
| 744 | else { | ||
| 745 | ✗ | for (int i=0; i<num_args; ++i) { | |
| 746 | ✗ | args_before[i] = _args_before[i]; | |
| 747 | ✗ | args_after[i] = _args_after[i]; | |
| 748 | } | ||
| 749 | } | ||
| 750 | |||
| 751 | ✗ | memset(cache_stage, -1, sizeof(cache_stage)); | |
| 752 | |||
| 753 | // first clip with starting parameter values | ||
| 754 | ✗ | cache[0] = env->Invoke(name, AVSValue(args_before.data(), num_args)).AsClip(); | |
| 755 | ✗ | cache_stage[0] = 0; | |
| 756 | ✗ | VideoInfo vi1 = cache[0]->GetVideoInfo(); | |
| 757 | |||
| 758 | ✗ | if (range_limit) { | |
| 759 | ✗ | VideoInfo vi = context->GetVideoInfo(); | |
| 760 | |||
| 761 | ✗ | if (vi.width != vi1.width || vi.height != vi1.height) | |
| 762 | ✗ | env->ThrowError("ApplyRange: Filtered and unfiltered video frame sizes must match"); | |
| 763 | |||
| 764 | ✗ | if (!vi.IsSameColorspace(vi1)) | |
| 765 | ✗ | env->ThrowError("ApplyRange: Filtered and unfiltered video colorspace must match"); | |
| 766 | } | ||
| 767 | else { | ||
| 768 | // last clip with ending parameter values | ||
| 769 | ✗ | cache[1] = env->Invoke(name, AVSValue(args_after, num_args)).AsClip(); | |
| 770 | ✗ | cache_stage[1] = last-first; | |
| 771 | ✗ | VideoInfo vi2 = cache[1]->GetVideoInfo(); | |
| 772 | |||
| 773 | ✗ | if (vi1.width != vi2.width || vi1.height != vi2.height) | |
| 774 | ✗ | env->ThrowError("Animate: initial and final video frame sizes must match"); | |
| 775 | } | ||
| 776 | ✗ | } | |
| 777 | |||
| 778 | |||
| 779 | ✗ | bool __stdcall Animate::GetParity(int n) | |
| 780 | { | ||
| 781 | ✗ | if (range_limit) { | |
| 782 | ✗ | if ((n<first) || (n>last)) { | |
| 783 | ✗ | return args_after[0].AsClip()->GetParity(n); | |
| 784 | } | ||
| 785 | } | ||
| 786 | // We could go crazy here and replicate the GetFrame | ||
| 787 | // logic and share the cache_stage but it is not | ||
| 788 | // really worth it. Although clips that change parity | ||
| 789 | // are supported they are very confusing. | ||
| 790 | ✗ | return cache[0]->GetParity(n); | |
| 791 | } | ||
| 792 | |||
| 793 | // 96.32 bit arithmetic helper stuff for Animate | ||
| 794 | |||
| 795 | // 128-bit integer structure | ||
| 796 | struct my_int128_t { | ||
| 797 | int64_t H; // High 64 bits (includes sign) | ||
| 798 | uint64_t L; // Low 64 bits (unsigned) | ||
| 799 | }; | ||
| 800 | |||
| 801 | // Function to multiply two 64-bit values and produce a 128-bit result | ||
| 802 | ✗ | static inline my_int128_t mul_64_64_to_128(int64_t a, int64_t b) { | |
| 803 | my_int128_t result; | ||
| 804 | |||
| 805 | // Handle sign separately | ||
| 806 | ✗ | uint64_t sign = ((a < 0) ^ (b < 0)) ? 1 : 0; | |
| 807 | |||
| 808 | // Use absolute values for multiplication | ||
| 809 | ✗ | uint64_t abs_a = (a < 0) ? -a : a; | |
| 810 | ✗ | uint64_t abs_b = (b < 0) ? -b : b; | |
| 811 | |||
| 812 | // Multiply using 32-bit parts to avoid overflow | ||
| 813 | ✗ | uint64_t a_lo = abs_a & 0xFFFFFFFF; | |
| 814 | ✗ | uint64_t a_hi = abs_a >> 32; | |
| 815 | ✗ | uint64_t b_lo = abs_b & 0xFFFFFFFF; | |
| 816 | ✗ | uint64_t b_hi = abs_b >> 32; | |
| 817 | |||
| 818 | // Multiply the components | ||
| 819 | ✗ | uint64_t lo_lo = a_lo * b_lo; | |
| 820 | ✗ | uint64_t hi_lo = a_hi * b_lo; | |
| 821 | ✗ | uint64_t lo_hi = a_lo * b_hi; | |
| 822 | ✗ | uint64_t hi_hi = a_hi * b_hi; | |
| 823 | |||
| 824 | // Combine the results | ||
| 825 | ✗ | uint64_t mid = (lo_lo >> 32) + (hi_lo & 0xFFFFFFFF) + (lo_hi & 0xFFFFFFFF); | |
| 826 | ✗ | uint64_t carry = mid >> 32; | |
| 827 | |||
| 828 | ✗ | result.L = (lo_lo & 0xFFFFFFFF) | ((mid & 0xFFFFFFFF) << 32); | |
| 829 | ✗ | result.H = (hi_lo >> 32) + (lo_hi >> 32) + hi_hi + carry; | |
| 830 | |||
| 831 | // Apply sign if needed | ||
| 832 | ✗ | if (sign) { | |
| 833 | // Two's complement negation | ||
| 834 | ✗ | result.L = ~result.L + 1; | |
| 835 | ✗ | result.H = ~result.H + (result.L == 0); | |
| 836 | } | ||
| 837 | |||
| 838 | ✗ | return result; | |
| 839 | } | ||
| 840 | |||
| 841 | // Function to add two 128-bit integers | ||
| 842 | ✗ | static inline my_int128_t add_128(my_int128_t a, my_int128_t b) { | |
| 843 | my_int128_t result; | ||
| 844 | |||
| 845 | ✗ | result.L = a.L + b.L; | |
| 846 | // Check for carry | ||
| 847 | ✗ | result.H = a.H + b.H + (result.L < a.L ? 1 : 0); | |
| 848 | |||
| 849 | ✗ | return result; | |
| 850 | } | ||
| 851 | |||
| 852 | // Function to shift a 128-bit integer right by 'shift' bits | ||
| 853 | ✗ | static inline int64_t shift_right_128(my_int128_t a, int shift) { | |
| 854 | ✗ | if (shift >= 64) { | |
| 855 | // If shifting by 64 or more, the result comes entirely from the high part | ||
| 856 | ✗ | return a.H >> (shift - 64); | |
| 857 | } | ||
| 858 | else { | ||
| 859 | // Combine portions from both high and low parts | ||
| 860 | ✗ | return (a.H << (64 - shift)) | (a.L >> shift); | |
| 861 | } | ||
| 862 | } | ||
| 863 | |||
| 864 | // Constants for 96.32 integer arithmetic | ||
| 865 | constexpr int ANIMATE_INT_ARITH_SCALEBITS = 32; | ||
| 866 | constexpr uint64_t ANIMATE_FULL_SCALE = 1ULL << ANIMATE_INT_ARITH_SCALEBITS; // 2^32 | ||
| 867 | constexpr uint64_t ANIMATE_ROUND = ANIMATE_FULL_SCALE >> 1; // 2^31 | ||
| 868 | |||
| 869 | // Main interpolation function with uint64_t factor | ||
| 870 | // factor = 2^32 means 1.0 | ||
| 871 | ✗ | static int64_t Muldiv_64_32_integer_arithm(int64_t a, int64_t b, uint64_t factor) { | |
| 872 | |||
| 873 | // Special case handling for factor = 0 or factor = 2^32 (0.0 or 1.0) | ||
| 874 | ✗ | if (factor == 0) | |
| 875 | ✗ | return a; | |
| 876 | ✗ | else if (factor == ANIMATE_FULL_SCALE) | |
| 877 | ✗ | return b; | |
| 878 | |||
| 879 | // Calculate the interpolation | ||
| 880 | // (a*(1-factor) + b*factor + round) / fullscale | ||
| 881 | |||
| 882 | my_int128_t temp_a, temp_b; | ||
| 883 | ✗ | temp_a = mul_64_64_to_128(a, ANIMATE_FULL_SCALE - factor); | |
| 884 | ✗ | temp_b = mul_64_64_to_128(b, factor); | |
| 885 | ✗ | my_int128_t temp = add_128(temp_a, temp_b); | |
| 886 | |||
| 887 | // Add rounding constant | ||
| 888 | ✗ | temp.L += ANIMATE_ROUND; | |
| 889 | // Check for carry | ||
| 890 | ✗ | if (temp.L < ANIMATE_ROUND) { | |
| 891 | ✗ | temp.H++; | |
| 892 | } | ||
| 893 | |||
| 894 | // Back to the real integer domain | ||
| 895 | ✗ | return shift_right_128(temp, ANIMATE_INT_ARITH_SCALEBITS); | |
| 896 | } | ||
| 897 | |||
| 898 | ✗ | PVideoFrame __stdcall Animate::GetFrame(int n, IScriptEnvironment* env) | |
| 899 | { | ||
| 900 | ✗ | if (range_limit) { | |
| 901 | ✗ | if ((n<first) || (n>last)) { | |
| 902 | ✗ | return args_after[0].AsClip()->GetFrame(n, env); | |
| 903 | } | ||
| 904 | ✗ | return cache[0]->GetFrame(n, env); | |
| 905 | } | ||
| 906 | ✗ | int stage = clamp(n, first, last) - first; | |
| 907 | ✗ | for (int i=0; i<cache_size; ++i) | |
| 908 | ✗ | if (cache_stage[i] == stage) | |
| 909 | ✗ | return cache[i]->GetFrame(n, env); | |
| 910 | |||
| 911 | // filter not found in cache--create it | ||
| 912 | ✗ | int furthest = 0; | |
| 913 | ✗ | for (int j=1; j<cache_size; ++j) | |
| 914 | ✗ | if (abs(stage-cache_stage[j]) > abs(stage-cache_stage[furthest])) | |
| 915 | ✗ | furthest = j; | |
| 916 | |||
| 917 | ✗ | int scale = last-first; | |
| 918 | ✗ | double stage_mod = (double)stage / scale; // linear 0.0 .. 1.0 | |
| 919 | |||
| 920 | ✗ | if (custom_fn.IsFunction()) { | |
| 921 | // custom function maps x -> fn(x), so that x=0.0 and x=1.0 still returns 0.0 and 1.0 (ideally) | ||
| 922 | ✗ | PFunction func = custom_fn.AsFunction(); | |
| 923 | ✗ | const char* argnames[1] = { "stage" }; | |
| 924 | ✗ | AVSValue actual_args[1] = { stage_mod }; | |
| 925 | ✗ | const AVSValue actual_args_array = AVSValue(actual_args, 1); | |
| 926 | ✗ | stage_mod = env->Invoke3(AVSValue(), func, actual_args_array, argnames).AsFloat(); | |
| 927 | // Used for integer interpolations as well. | ||
| 928 | // Since normalization is at 2^32 (where 1.0 equals 2^32), and int64_t is used, | ||
| 929 | // it can handle up to a 2^31 multiplier from fn(x). | ||
| 930 | // However, a return value range of 0.0 to 1.0 is ideal. | ||
| 931 | ✗ | } | |
| 932 | |||
| 933 | ✗ | const uint64_t stage_int_arith = (int64_t)(double(ANIMATE_FULL_SCALE) * stage_mod); | |
| 934 | |||
| 935 | ✗ | for (int a = 0; a < num_args; ++a) { | |
| 936 | ✗ | if (args_before[a].IsInt() && args_after[a].IsInt()) { | |
| 937 | // 96.32 bit arithmetic, intermediate 128 bits inside. | ||
| 938 | // Uses proper rounding when returning to real integer domain. | ||
| 939 | ✗ | int64_t start = args_before[a].AsLong(); | |
| 940 | ✗ | int64_t end = args_after[a].AsLong(); | |
| 941 | ✗ | int64_t interpolated_value = Muldiv_64_32_integer_arithm(start, end, stage_int_arith); | |
| 942 | ✗ | int64_t lower_bound = std::min(start, end); | |
| 943 | ✗ | int64_t upper_bound = std::max(start, end); | |
| 944 | // rounding error can occur, so that the intermediate result is not between the two values | ||
| 945 | ✗ | if (interpolated_value < lower_bound) { | |
| 946 | ✗ | interpolated_value = lower_bound; | |
| 947 | } | ||
| 948 | ✗ | else if (interpolated_value > upper_bound) { | |
| 949 | ✗ | interpolated_value = upper_bound; | |
| 950 | } | ||
| 951 | ✗ | args_now[a] = interpolated_value; | |
| 952 | } | ||
| 953 | ✗ | else if (args_before[a].IsFloat() && args_after[a].IsFloat()) { | |
| 954 | // note: AsFloat() returns double | ||
| 955 | ✗ | double start = args_before[a].AsFloat(); | |
| 956 | ✗ | double end = args_after[a].AsFloat(); | |
| 957 | ✗ | double interpolated_value = start * (1 - stage_mod) + end * stage_mod; | |
| 958 | ✗ | double lower_bound = std::min(start, end); | |
| 959 | ✗ | double upper_bound = std::max(start, end); | |
| 960 | // rounding error can occur, so that the intermediate result is not between the two values | ||
| 961 | ✗ | if (interpolated_value < lower_bound) { | |
| 962 | ✗ | interpolated_value = lower_bound; | |
| 963 | } | ||
| 964 | ✗ | else if (interpolated_value > upper_bound) { | |
| 965 | ✗ | interpolated_value = upper_bound; | |
| 966 | } | ||
| 967 | ✗ | args_now[a] = interpolated_value; | |
| 968 | } | ||
| 969 | else { | ||
| 970 | ✗ | args_now[a] = args_before[a]; // bool, string, etc.. no transition | |
| 971 | } | ||
| 972 | } | ||
| 973 | #if 0 | ||
| 974 | // old classic linear, 32 bit only kept for reference | ||
| 975 | int scale = last - first; | ||
| 976 | |||
| 977 | for (int a=0; a<num_args; ++a) { | ||
| 978 | if (args_before[a].IsInt() && args_after[a].IsInt()) { | ||
| 979 | // no rounding here | ||
| 980 | args_now[a] = int((Int32x32To64(args_before[a].AsInt(), scale-stage) + Int32x32To64(args_after[a].AsInt(), stage)) / scale); | ||
| 981 | } | ||
| 982 | else if (args_before[a].IsFloat() && args_after[a].IsFloat()) { | ||
| 983 | args_now[a] = (args_before[a].AsFloat()*(scale-stage) + args_after[a].AsFloat()*stage) / scale; | ||
| 984 | } | ||
| 985 | else { | ||
| 986 | args_now[a] = args_before[a]; | ||
| 987 | } | ||
| 988 | } | ||
| 989 | #endif | ||
| 990 | ✗ | cache_stage[furthest] = stage; | |
| 991 | ✗ | cache[furthest] = env->Invoke(name, AVSValue(args_now, num_args)).AsClip(); | |
| 992 | ✗ | return cache[furthest]->GetFrame(n, env); | |
| 993 | } | ||
| 994 | |||
| 995 | ✗ | void __stdcall Animate::GetAudio(void* buf, int64_t start, int64_t count, IScriptEnvironment* env) { | |
| 996 | ✗ | if (range_limit) { // Applyrange - hard switch between streams. | |
| 997 | |||
| 998 | ✗ | const VideoInfo& vi1 = cache[0]->GetVideoInfo(); | |
| 999 | ✗ | const int64_t start_switch = vi1.AudioSamplesFromFrames(first); | |
| 1000 | ✗ | const int64_t end_switch = vi1.AudioSamplesFromFrames(last+1); | |
| 1001 | |||
| 1002 | ✗ | if ( (start+count <= start_switch) || (start >= end_switch) ) { | |
| 1003 | // Everything unfiltered | ||
| 1004 | ✗ | args_after[0].AsClip()->GetAudio(buf, start, count, env); | |
| 1005 | ✗ | return; | |
| 1006 | } | ||
| 1007 | ✗ | else if ( (start < start_switch) || (start+count > end_switch) ) { | |
| 1008 | // We are at one or both switchover points | ||
| 1009 | |||
| 1010 | // The bit before | ||
| 1011 | ✗ | if (start_switch > start) { | |
| 1012 | ✗ | const int64_t pre_count = start_switch - start; | |
| 1013 | ✗ | args_after[0].AsClip()->GetAudio(buf, start, pre_count, env); // UnFiltered | |
| 1014 | ✗ | start += pre_count; | |
| 1015 | ✗ | count -= pre_count; | |
| 1016 | ✗ | buf = (void*)( (BYTE*)buf + vi1.BytesFromAudioSamples(pre_count) ); | |
| 1017 | } | ||
| 1018 | |||
| 1019 | // The bit in the middle | ||
| 1020 | ✗ | const int64_t filt_count = (end_switch < start+count) ? (end_switch - start) : count; | |
| 1021 | ✗ | cache[0]->GetAudio(buf, start, filt_count, env); // Filtered | |
| 1022 | ✗ | start += filt_count; | |
| 1023 | ✗ | count -= filt_count; | |
| 1024 | ✗ | buf = (void*)( (BYTE*)buf + vi1.BytesFromAudioSamples(filt_count) ); | |
| 1025 | |||
| 1026 | // The bit after | ||
| 1027 | ✗ | if (count > 0) | |
| 1028 | ✗ | args_after[0].AsClip()->GetAudio(buf, start, count, env); // UnFiltered | |
| 1029 | |||
| 1030 | ✗ | return; | |
| 1031 | } | ||
| 1032 | // Everything filtered | ||
| 1033 | } | ||
| 1034 | ✗ | cache[0]->GetAudio(buf, start, count, env); // Filtered | |
| 1035 | } | ||
| 1036 | |||
| 1037 | |||
| 1038 | ✗ | AVSValue __cdecl Animate::Create(AVSValue args, void* user_data, IScriptEnvironment* env) | |
| 1039 | { | ||
| 1040 | ✗ | auto anim_kind = reinterpret_cast<intptr_t>(user_data); | |
| 1041 | // 0: legacy 1: extra callback function parameter | ||
| 1042 | |||
| 1043 | ✗ | PClip context; | |
| 1044 | // When function parameter exists, it shifts the parameter array by one | ||
| 1045 | ✗ | const int param_index = (anim_kind == 0) ? 3 : 4; | |
| 1046 | // Convert the clip-at-zeroth-param version to the other signature. | ||
| 1047 | ✗ | if (args[0].IsClip()) { | |
| 1048 | // ciis.* -> iis.* | ||
| 1049 | // ciisn.* -> iisn.* | ||
| 1050 | ✗ | context = args[0].AsClip(); | |
| 1051 | ✗ | args = AVSValue(&args[1], param_index + 1); | |
| 1052 | } | ||
| 1053 | ✗ | const int first = args[0].AsInt(); | |
| 1054 | ✗ | const int last = args[1].AsInt(); | |
| 1055 | ✗ | const char* const name = args[2].AsString(); | |
| 1056 | |||
| 1057 | ✗ | int n = args[param_index].ArraySize(); | |
| 1058 | ✗ | if (n&1) | |
| 1059 | ✗ | env->ThrowError("Animate: must have two argument lists of the same length"); // two sets | |
| 1060 | ✗ | return new Animate(context, first, last, name, &args[param_index][0], &args[param_index][n>>1], n>>1, false, | |
| 1061 | ✗ | anim_kind == 0 ? AVSValue() : args[3], env); | |
| 1062 | ✗ | } | |
| 1063 | |||
| 1064 | |||
| 1065 | ✗ | AVSValue __cdecl Animate::Create_Range(AVSValue args, void*, IScriptEnvironment* env) | |
| 1066 | { | ||
| 1067 | ✗ | PClip context = args[0].AsClip(); | |
| 1068 | |||
| 1069 | ✗ | const int first = args[1].AsInt(); | |
| 1070 | ✗ | const int last = args[2].AsInt(); | |
| 1071 | ✗ | const char* const name = args[3].AsString(); | |
| 1072 | ✗ | int n = args[4].ArraySize(); | |
| 1073 | ✗ | return new Animate(context, first, last, name, &args[4][0], &args[4][0], n, true, AVSValue(), env); | |
| 1074 | ✗ | } | |
| 1075 |