GCC Code Coverage Report


Directory: avs_core/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 8.9% 49 / 0 / 550
Functions: 9.1% 2 / 0 / 22
Branches: 3.8% 45 / 0 / 1195

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