GCC Code Coverage Report


Directory: avs_core/
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 13.2% 156 / 0 / 1184
Functions: 13.9% 5 / 0 / 36
Branches: 7.0% 152 / 0 / 2179

filters/text-overlay.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 <avs/config.h>
36 #ifdef AVS_WINDOWS
37 #include <avs/win.h>
38 #else
39 #include <avs/posix.h>
40 #endif
41
42 #include "text-overlay.h"
43 #include "getalpharect_impl.h"
44 #include "getalpharect_scalar.h"
45 #ifdef INTEL_INTRINSICS
46 #include "intel/text-overlay_sse.h"
47 #ifdef INTEL_INTRINSICS_AVX512
48 #include "intel/getalpharect_avx512.h"
49 #endif
50 #include "overlay/intel/masked_rowprep_sse41.h"
51 #include "overlay/intel/masked_rowprep_avx2.h"
52 #endif
53 #include "../convert/convert_matrix.h" // for RGB2YUV_Rec601
54 #include "../convert/convert_helper.h" // chroma location
55
56 #define __STDC_FORMAT_MACROS
57 #include <inttypes.h>
58 #include <sstream>
59 #include <cstdint>
60 #include <cmath>
61 #include <algorithm>
62 #include <avs/minmax.h>
63 #include "../core/internal.h"
64 #include "../core/info.h"
65 #include "../core/strings.h"
66 #include "../core/audio.h"
67 #include <bitset>
68
69
70 #if defined(AVS_WINDOWS) && !defined(NO_WIN_GDI)
71 static HFONT LoadFont(const char name[], int size, bool bold, bool italic, int width=0, int angle=0)
72 {
73 return CreateFont( size, width, angle, angle, bold ? FW_BOLD : FW_NORMAL,
74 italic, FALSE, FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
75 CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, FF_DONTCARE | FIXED_PITCH /*FF_DONTCARE | DEFAULT_PITCH*/, name );
76 // avs+: force fixed pitch when font is not found by name
77 }
78 #endif
79
80 /********************************************************************
81 ***** Declare index of new filters for Avisynth's filter engine *****
82 ********************************************************************/
83
84 extern const AVSFunction Text_filters[] = {
85 { "ShowFrameNumber",BUILTIN_FUNC_PREFIX,
86 "c[scroll]b[offset]i[x]f[y]f[font]s[size]f[text_color]i[halo_color]i[font_width]f[font_angle]f[bold]b[italic]b[noaa]b[gdi]b",
87 ShowFrameNumber::Create },
88
89 { "ShowCRC32",BUILTIN_FUNC_PREFIX,
90 "c[scroll]b[offset]i[x]f[y]f[font]s[size]f[text_color]i[halo_color]i[font_width]f[font_angle]f[bold]b[italic]b[noaa]b[channels]s[mode]i[showmode]i[gdi]b",
91 ShowCRC32::Create },
92
93 { "ShowSMPTE",BUILTIN_FUNC_PREFIX,
94 "c[fps]f[offset]s[offset_f]i[x]f[y]f[font]s[size]f[text_color]i[halo_color]i[font_width]f[font_angle]f[bold]b[italic]b[noaa]b[gdi]b",
95 ShowSMPTE::CreateSMTPE },
96
97 { "ShowTime",BUILTIN_FUNC_PREFIX,
98 "c[offset_f]i[x]f[y]f[font]s[size]f[text_color]i[halo_color]i[font_width]f[font_angle]f[bold]b[italic]b[noaa]b[gdi]b",
99 ShowSMPTE::CreateTime },
100
101 { "Info", BUILTIN_FUNC_PREFIX, "c[font]s[size]f[text_color]i[halo_color]i[bold]b[italic]b[noaa]b[cpu]b[x]f[y]f[align]i[gdi]b", FilterInfo::Create }, // clip
102
103 { "Subtitle",BUILTIN_FUNC_PREFIX,
104 "cs[x]f[y]f[first_frame]i[last_frame]i[font]s[size]f[text_color]i[halo_color]i"
105 "[align]i[spc]i[lsp]i[font_width]f[font_angle]f[interlaced]b[font_filename]s[utf8]b[bold]b[italic]b[noaa]b[placement]s[gdi]b",
106 #if defined(AVS_WINDOWS) && !defined(NO_WIN_GDI)
107 Subtitle::Create
108 #else
109 SimpleText::Create // poor man's SubTitle, it's simulated with SimpleText
110 #endif
111 }, // see docs!
112
113 { "Compare",BUILTIN_FUNC_PREFIX,
114 "cc[channels]s[logfile]s[show_graph]b[gdi]b",
115 Compare::Create },
116
117 { "Text",BUILTIN_FUNC_PREFIX,
118 "cs[x]f[y]f[first_frame]i[last_frame]i[font]s[size]f[text_color]i[halo_color]i"
119 "[align]i[spc]i[lsp]i[font_width]f[font_angle]f[interlaced]b[font_filename]s[utf8]b[bold]b[italic]b[noaa]b[placement]s[gdi]b",
120 SimpleText::Create },
121
122 { 0 }
123 };
124
125
126
127
128
129 #if defined(AVS_WINDOWS) && !defined(NO_WIN_GDI)
130 /******************************
131 ******* Anti-alias ******
132 *****************************/
133
134 // Select the best rowprep function for soa_mask_mode at construction time.
135 using rowprep_u16_fn_t = const uint16_t*(*)(const uint16_t*, int, int, std::vector<uint16_t>&, int, int, MagicDiv);
136
137 static rowprep_u16_fn_t select_rowprep_u16(MaskMode mode, int64_t cpuFlags)
138 {
139 #ifdef INTEL_INTRINSICS
140 if (cpuFlags & CPUF_AVX2) {
141 switch (mode) {
142 case MASK444: return prepare_effective_mask_for_row_avx2<MASK444, uint16_t, true>;
143 case MASK411: return prepare_effective_mask_for_row_avx2<MASK411, uint16_t, true>;
144 case MASK420: return prepare_effective_mask_for_row_avx2<MASK420, uint16_t, true>;
145 case MASK420_MPEG2: return prepare_effective_mask_for_row_avx2<MASK420_MPEG2, uint16_t, true>;
146 case MASK420_TOPLEFT: return prepare_effective_mask_for_row_avx2<MASK420_TOPLEFT, uint16_t, true>;
147 case MASK422: return prepare_effective_mask_for_row_avx2<MASK422, uint16_t, true>;
148 case MASK422_MPEG2: return prepare_effective_mask_for_row_avx2<MASK422_MPEG2, uint16_t, true>;
149 case MASK422_TOPLEFT: return prepare_effective_mask_for_row_avx2<MASK422_TOPLEFT, uint16_t, true>;
150 }
151 }
152 if (cpuFlags & CPUF_SSE4_1) {
153 switch (mode) {
154 case MASK444: return prepare_effective_mask_for_row_sse41<MASK444, uint16_t, true>;
155 case MASK411: return prepare_effective_mask_for_row_sse41<MASK411, uint16_t, true>;
156 case MASK420: return prepare_effective_mask_for_row_sse41<MASK420, uint16_t, true>;
157 case MASK420_MPEG2: return prepare_effective_mask_for_row_sse41<MASK420_MPEG2, uint16_t, true>;
158 case MASK420_TOPLEFT: return prepare_effective_mask_for_row_sse41<MASK420_TOPLEFT, uint16_t, true>;
159 case MASK422: return prepare_effective_mask_for_row_sse41<MASK422, uint16_t, true>;
160 case MASK422_MPEG2: return prepare_effective_mask_for_row_sse41<MASK422_MPEG2, uint16_t, true>;
161 case MASK422_TOPLEFT: return prepare_effective_mask_for_row_sse41<MASK422_TOPLEFT, uint16_t, true>;
162 }
163 }
164 #else
165 (void)cpuFlags;
166 #endif
167 switch (mode) {
168 case MASK444: return prepare_effective_mask_for_row<MASK444, uint16_t, true>;
169 case MASK411: return prepare_effective_mask_for_row<MASK411, uint16_t, true>;
170 case MASK420: return prepare_effective_mask_for_row<MASK420, uint16_t, true>;
171 case MASK420_MPEG2: return prepare_effective_mask_for_row<MASK420_MPEG2, uint16_t, true>;
172 case MASK420_TOPLEFT: return prepare_effective_mask_for_row<MASK420_TOPLEFT, uint16_t, true>;
173 case MASK422: return prepare_effective_mask_for_row<MASK422, uint16_t, true>;
174 case MASK422_MPEG2: return prepare_effective_mask_for_row<MASK422_MPEG2, uint16_t, true>;
175 case MASK422_TOPLEFT: return prepare_effective_mask_for_row<MASK422_TOPLEFT, uint16_t, true>;
176 }
177 return nullptr;
178 }
179
180 Antialiaser::Antialiaser(int width, int height, const char fontname[], int size,
181 int _textcolor, int _halocolor, bool _bold, bool _italic, bool _noaa,
182 int64_t cpuFlags,
183 int _chromaplacement,
184 int font_width, int font_angle, bool _interlaced) :
185 soa_buf(nullptr), w_stride(0),
186 chromaplacement(_chromaplacement),
187 w(width), h(height), textcolor(_textcolor), halocolor(_halocolor),
188 dirty(true), interlaced(_interlaced),
189 bold(_bold), italic(_italic), noaa(_noaa)
190 {
191 // row preparation functions convert a luma mask to chroma masks with correct chroma placement
192 // for subsampled formats.
193 // Pre-select SIMD rowprep for all 8 MaskModes (411, 420 and 422 variants, 444)
194 for (int m = 0; m < 8; ++m)
195 rowprep_fns[m] = select_rowprep_u16(static_cast<MaskMode>(m), cpuFlags);
196
197 #ifdef INTEL_INTRINSICS
198 // Select best GetAlphaRect implementation (noaa and interlaced baked in via template)
199 getalpharect_fn = nullptr;
200 #ifdef INTEL_INTRINSICS_AVX512
201 if (cpuFlags & CPUF_AVX512_FAST)
202 getalpharect_fn = GetAlphaRect_select_avx512(_noaa, _interlaced);
203 #endif
204 if (!getalpharect_fn && (cpuFlags & CPUF_AVX2))
205 getalpharect_fn = GetAlphaRect_select_avx2(_noaa, _interlaced);
206 if (!getalpharect_fn && (cpuFlags & CPUF_SSE4_1))
207 getalpharect_fn = GetAlphaRect_select_sse41(_noaa, _interlaced);
208 // nullptr means scalar fallback (called directly inside GetAlphaRect())
209 #endif
210
211 struct {
212 BITMAPINFOHEADER bih;
213 RGBQUAD clr[2];
214 } b;
215
216 b.bih.biSize = sizeof(BITMAPINFOHEADER);
217 b.bih.biWidth = width * 8 + 32;
218 b.bih.biHeight = height * 8 + 32;
219 b.bih.biBitCount = 1;
220 b.bih.biPlanes = 1;
221 b.bih.biCompression = BI_RGB;
222 b.bih.biXPelsPerMeter = 0;
223 b.bih.biYPelsPerMeter = 0;
224 b.bih.biClrUsed = 2;
225 b.bih.biClrImportant = 2;
226 b.clr[0].rgbBlue = b.clr[0].rgbGreen = b.clr[0].rgbRed = 0;
227 b.clr[1].rgbBlue = b.clr[1].rgbGreen = b.clr[1].rgbRed = 255;
228
229 hdcAntialias = CreateCompatibleDC(NULL);
230 if (hdcAntialias) {
231 hbmAntialias = CreateDIBSection
232 ( hdcAntialias,
233 (BITMAPINFO *)&b,
234 DIB_RGB_COLORS,
235 &lpAntialiasBits,
236 NULL,
237 0 );
238 if (hbmAntialias) {
239 hbmDefault = (HBITMAP)SelectObject(hdcAntialias, hbmAntialias);
240 HFONT newfont = LoadFont(fontname, size, bold, italic, font_width, font_angle);
241 hfontDefault = newfont ? (HFONT)SelectObject(hdcAntialias, newfont) : 0;
242
243 SetMapMode(hdcAntialias, MM_TEXT);
244 SetTextColor(hdcAntialias, 0xffffff);
245 SetBkColor(hdcAntialias, 0);
246
247 // 64 byte boundary of the four planes for row-interleaved SoA
248 // One single buffer allocation for multiple planes,
249 w_stride = (width + 31) & ~31;
250 soa_buf = new(std::nothrow) uint16_t[w_stride * height * 4];
251 if (!soa_buf) {
252 FreeDC();
253 } else {
254 uv_buf_ba.resize(width);
255 uv_buf_u.resize(width);
256 uv_buf_v.resize(width);
257 }
258 }
259 }
260 }
261
262
263 Antialiaser::~Antialiaser() {
264 FreeDC();
265 delete[] soa_buf;
266 }
267
268
269 HDC Antialiaser::GetDC() {
270 dirty = true;
271 return hdcAntialias;
272 }
273
274
275 void Antialiaser::FreeDC() {
276 if (hdcAntialias) { // :FIXME: Interlocked
277 if (hbmDefault) {
278 DeleteObject(SelectObject(hdcAntialias, hbmDefault));
279 hbmDefault = 0;
280 }
281 if (hfontDefault) {
282 DeleteObject(SelectObject(hdcAntialias, hfontDefault));
283 hfontDefault = 0;
284 }
285 DeleteDC(hdcAntialias);
286 hdcAntialias = 0;
287 }
288 }
289
290
291 void Antialiaser::Apply(const VideoInfo& vi, PVideoFrame* frame, int pitch)
292 {
293 if (!soa_buf) return;
294
295 if (vi.IsRGB32())
296 ApplyRGB_packed<uint8_t, true>((*frame)->GetWritePtr(), pitch);
297 else if (vi.IsRGB64())
298 ApplyRGB_packed<uint16_t, true>((*frame)->GetWritePtr(), pitch);
299 else if (vi.IsRGB24())
300 ApplyRGB_packed<uint8_t, false>((*frame)->GetWritePtr(), pitch);
301 else if (vi.IsRGB48())
302 ApplyRGB_packed<uint16_t, false>((*frame)->GetWritePtr(), pitch);
303 else if (vi.IsYUY2())
304 ApplyYUY2((*frame)->GetWritePtr(), pitch);
305 else if (vi.IsPlanar()) {
306 const bool isRGB = vi.IsPlanarRGB() || vi.IsPlanarRGBA();
307 BYTE* bufY = isRGB ? (*frame)->GetWritePtr(PLANAR_R) : (*frame)->GetWritePtr();
308 BYTE* bufU = nullptr;
309 BYTE* bufV = nullptr;
310 int pitchUV = 0;
311 if (vi.NumComponents() > 1) {
312 pitchUV = isRGB ? (*frame)->GetPitch(PLANAR_G) : (*frame)->GetPitch(PLANAR_U);
313 bufU = isRGB ? (*frame)->GetWritePtr(PLANAR_G) : (*frame)->GetWritePtr(PLANAR_U);
314 bufV = isRGB ? (*frame)->GetWritePtr(PLANAR_B) : (*frame)->GetWritePtr(PLANAR_V);
315 }
316 const int bpp = vi.BitsPerComponent();
317 #define CALL_SOA(mode) \
318 switch (bpp) { \
319 case 8: ApplyPlanar_SoA<mode, 8>(bufY, pitch, pitchUV, bufU, bufV, isRGB); break; \
320 case 10: ApplyPlanar_SoA<mode, 10>(bufY, pitch, pitchUV, bufU, bufV, isRGB); break; \
321 case 12: ApplyPlanar_SoA<mode, 12>(bufY, pitch, pitchUV, bufU, bufV, isRGB); break; \
322 case 14: ApplyPlanar_SoA<mode, 14>(bufY, pitch, pitchUV, bufU, bufV, isRGB); break; \
323 case 16: ApplyPlanar_SoA<mode, 16>(bufY, pitch, pitchUV, bufU, bufV, isRGB); break; \
324 case 32: ApplyPlanar_SoA<mode, 32>(bufY, pitch, pitchUV, bufU, bufV, isRGB); break; \
325 }
326 // Compute MaskMode from clip subsampling + stored chromaplacement
327 MaskMode mode = MASK444;
328 if ((vi.IsYUV() || vi.IsYUVA()) && !vi.IsY()) {
329 const int sx = vi.GetPlaneWidthSubsampling(PLANAR_U);
330 const int sy = vi.GetPlaneHeightSubsampling(PLANAR_U);
331 if (sx == 2) {
332 mode = MASK411; // always center averaging
333 } else if (sx == 1 && sy == 1) {
334 switch (chromaplacement) {
335 case ChromaLocation_e::AVS_CHROMA_LEFT: mode = MASK420_MPEG2; break;
336 case ChromaLocation_e::AVS_CHROMA_TOP_LEFT: mode = MASK420_TOPLEFT; break;
337 default: mode = MASK420; break; // center
338 }
339 } else if (sx == 1 && sy == 0) {
340 switch (chromaplacement) {
341 case ChromaLocation_e::AVS_CHROMA_LEFT: mode = MASK422_MPEG2; break;
342 case ChromaLocation_e::AVS_CHROMA_TOP_LEFT: mode = MASK422_TOPLEFT; break;
343 default: mode = MASK422; break; // center
344 }
345 }
346 }
347 switch (mode) {
348 case MASK444: CALL_SOA(MASK444); break;
349 case MASK411: CALL_SOA(MASK411); break;
350 case MASK420: CALL_SOA(MASK420); break;
351 case MASK420_MPEG2: CALL_SOA(MASK420_MPEG2); break;
352 case MASK420_TOPLEFT: CALL_SOA(MASK420_TOPLEFT); break;
353 case MASK422: CALL_SOA(MASK422); break;
354 case MASK422_MPEG2: CALL_SOA(MASK422_MPEG2); break;
355 case MASK422_TOPLEFT: CALL_SOA(MASK422_TOPLEFT); break;
356 }
357 #undef CALL_SOA
358 }
359 }
360
361 // Hoping that bits_per_pixel template make it quicker
362 template<MaskMode maskMode, int bits_per_pixel>
363 void Antialiaser::ApplyPlanar_SoA(BYTE* buf, int pitch, int pitchUV, BYTE* bufU, BYTE* bufV, bool isRGB)
364 {
365 constexpr int shiftX =
366 (maskMode == MASK444) ? 0 :
367 (maskMode == MASK411) ? 2 : 1;
368 constexpr int shiftY =
369 (maskMode == MASK420 || maskMode == MASK420_MPEG2 || maskMode == MASK420_TOPLEFT) ? 1 : 0;
370 constexpr int stepX = 1 << shiftX;
371 constexpr int stepY = 1 << shiftY;
372
373 if (dirty) {
374 GetAlphaRect();
375 xl &= -stepX; xr |= stepX - 1;
376 yb &= -stepY; yt |= stepY - 1;
377 }
378 // The abbreviation 'ba' stands for basealpha.
379
380
381 // === Y/R plane — row-interleaved SoA: plane 0=ba (basealpha), 1=ry ===
382 const uint16_t* y_row_ptr = soa_buf + yb * 4 * w_stride;
383 BYTE* buf_row = buf + pitch * yb;
384
385 if constexpr (bits_per_pixel == 8) {
386 for (int y = yb; y <= yt; ++y) {
387 const uint16_t* ba_y = y_row_ptr;
388 const uint16_t* ry_y = y_row_ptr + w_stride;
389 for (int x = xl; x <= xr; ++x) {
390 const int ba = ba_y[x];
391 if (ba != 256)
392 buf_row[x] = BYTE((buf_row[x] * ba + ry_y[x]) >> 8);
393 }
394 buf_row += pitch;
395 y_row_ptr += 4 * w_stride;
396 }
397 } else if constexpr (bits_per_pixel >= 10 && bits_per_pixel <= 16) {
398 for (int y = yb; y <= yt; ++y) {
399 const uint16_t* ba_y = y_row_ptr;
400 const uint16_t* ry_y = y_row_ptr + w_stride;
401 uint16_t* p = reinterpret_cast<uint16_t*>(buf_row);
402 for (int x = xl; x <= xr; ++x) {
403 const int ba = ba_y[x];
404 if (ba != 256)
405 p[x] = (uint16_t)((p[x] * ba + (ry_y[x] << (bits_per_pixel - 8))) >> 8);
406 }
407 buf_row += pitch;
408 y_row_ptr += 4 * w_stride;
409 }
410 } else { // float: Y/R plane assumes 0..1 scale
411 for (int y = yb; y <= yt; ++y) {
412 const uint16_t* ba_y = y_row_ptr;
413 const uint16_t* ry_y = y_row_ptr + w_stride;
414 float* p = reinterpret_cast<float*>(buf_row);
415 for (int x = xl; x <= xr; ++x) {
416 const int ba = ba_y[x];
417 if (ba != 256)
418 p[x] = p[x] * ba / 256.0f + ry_y[x] / 65536.0f;
419 }
420 buf_row += pitch;
421 y_row_ptr += 4 * w_stride;
422 }
423 }
424
425 if (!bufU) return;
426
427 // === U/G and V/B planes — plane 2=u, 3=v; chroma-placement-aware downsampling ===
428 const int uv_width = w >> shiftX;
429 const int xl_uv = xl >> shiftX;
430 const int xr_uv = xr >> shiftX;
431 // row preparation functions take full-width luma mask rows and downsample to UV width with correct chroma placement, returning a pointer to the prepared row (which may be buf_ba or an internal static buffer).
432 // rowprep output buffers (unused for MASK444 — rowprep returns input ptr directly).
433 std::vector<uint16_t>& buf_ba = uv_buf_ba;
434 std::vector<uint16_t>& buf_u = uv_buf_u;
435 std::vector<uint16_t>& buf_v = uv_buf_v;
436
437 const uint16_t* uv_row_ptr = soa_buf + yb * 4 * w_stride;
438 BYTE* bufU_row = bufU + pitchUV * (yb >> shiftY);
439 BYTE* bufV_row = bufV + pitchUV * (yb >> shiftY);
440
441 // mask_pitch for 420 modes: next luma row of same plane is 4*w_stride elements ahead in SoA layout
442 const int soa_row_pitch = 4 * w_stride;
443
444 // +1 * w_stride was already handled: Y
445 if constexpr (bits_per_pixel == 8) {
446 for (int y = yb; y <= yt; y += stepY) {
447 const uint16_t* ba_row = rowprep_fns[maskMode](uv_row_ptr, soa_row_pitch, uv_width, buf_ba, 0, 0, {});
448 const uint16_t* u_row = rowprep_fns[maskMode](uv_row_ptr + 2 * w_stride, soa_row_pitch, uv_width, buf_u, 0, 0, {});
449 const uint16_t* v_row = rowprep_fns[maskMode](uv_row_ptr + 3 * w_stride, soa_row_pitch, uv_width, buf_v, 0, 0, {});
450 for (int xs = xl_uv; xs <= xr_uv; ++xs) {
451 const int ba = ba_row[xs];
452 if (ba != 256) {
453 bufU_row[xs] = BYTE((bufU_row[xs] * ba + u_row[xs]) >> 8);
454 bufV_row[xs] = BYTE((bufV_row[xs] * ba + v_row[xs]) >> 8);
455 }
456 }
457 bufU_row += pitchUV;
458 bufV_row += pitchUV;
459 uv_row_ptr += stepY * 4 * w_stride;
460 }
461 } else if constexpr (bits_per_pixel >= 10 && bits_per_pixel <= 16) {
462 for (int y = yb; y <= yt; y += stepY) {
463 const uint16_t* ba_row = rowprep_fns[maskMode](uv_row_ptr, soa_row_pitch, uv_width, buf_ba, 0, 0, {});
464 const uint16_t* u_row = rowprep_fns[maskMode](uv_row_ptr + 2 * w_stride, soa_row_pitch, uv_width, buf_u, 0, 0, {});
465 const uint16_t* v_row = rowprep_fns[maskMode](uv_row_ptr + 3 * w_stride, soa_row_pitch, uv_width, buf_v, 0, 0, {});
466 uint16_t* pU = reinterpret_cast<uint16_t*>(bufU_row);
467 uint16_t* pV = reinterpret_cast<uint16_t*>(bufV_row);
468 for (int xs = xl_uv; xs <= xr_uv; ++xs) {
469 const int ba = ba_row[xs];
470 if (ba != 256) {
471 pU[xs] = (uint16_t)((pU[xs] * ba + (u_row[xs] << (bits_per_pixel - 8))) >> 8);
472 pV[xs] = (uint16_t)((pV[xs] * ba + (v_row[xs] << (bits_per_pixel - 8))) >> 8);
473 }
474 }
475 bufU_row += pitchUV;
476 bufV_row += pitchUV;
477 uv_row_ptr += stepY * 4 * w_stride;
478 }
479 } else { // float UV
480 // 32-bit float UV neutral chroma is 0.0f
481 const float middle_shift = isRGB ? 0.0f : 0.5f; // yes, correction needed in and out
482 constexpr float scale_1_per_256 = 1.0f / 256.0f;
483 constexpr float scale_1_per_65536 = 1.0f / 65536.0f;
484 for (int y = yb; y <= yt; y += stepY) {
485 const uint16_t* ba_row = rowprep_fns[maskMode](uv_row_ptr, soa_row_pitch, uv_width, buf_ba, 0, 0, {});
486 const uint16_t* u_row = rowprep_fns[maskMode](uv_row_ptr + 2 * w_stride, soa_row_pitch, uv_width, buf_u, 0, 0, {});
487 const uint16_t* v_row = rowprep_fns[maskMode](uv_row_ptr + 3 * w_stride, soa_row_pitch, uv_width, buf_v, 0, 0, {});
488 float* pU = reinterpret_cast<float*>(bufU_row);
489 float* pV = reinterpret_cast<float*>(bufV_row);
490 for (int xs = xl_uv; xs <= xr_uv; ++xs) {
491 const int ba = ba_row[xs];
492 if (ba != 256) {
493 const float ba_f = ba * scale_1_per_256;
494 pU[xs] = (pU[xs] + middle_shift) * ba_f + u_row[xs] * scale_1_per_65536 - middle_shift;
495 pV[xs] = (pV[xs] + middle_shift) * ba_f + v_row[xs] * scale_1_per_65536 - middle_shift;
496 }
497 }
498 bufU_row += pitchUV;
499 bufV_row += pitchUV;
500 uv_row_ptr += stepY * 4 * w_stride;
501 }
502 }
503 }
504
505
506 void Antialiaser::ApplyYUY2(BYTE* buf, int pitch) {
507 if (dirty) {
508 GetAlphaRect();
509 xl &= -2; xr |= 1;
510 }
511 const uint16_t* row_ptr = soa_buf + yb * 4 * w_stride;
512 buf += pitch * yb;
513
514 for (int y = yb; y <= yt; ++y) {
515 const uint16_t* ba_row = row_ptr;
516 const uint16_t* ry_row = row_ptr + w_stride;
517 const uint16_t* u_row = row_ptr + 2 * w_stride;
518 const uint16_t* v_row = row_ptr + 3 * w_stride;
519 for (int x = xl; x <= xr; x += 2) {
520 const int ba0 = ba_row[x];
521 const int ba1 = ba_row[x + 1];
522 const int baUV = ba0 + ba1;
523
524 if (baUV != 512) {
525 buf[x*2+0] = BYTE((buf[x*2+0] * ba0 + ry_row[x]) >> 8);
526 buf[x*2+2] = BYTE((buf[x*2+2] * ba1 + ry_row[x + 1]) >> 8);
527
528 const int au = u_row[x] + u_row[x + 1];
529 buf[x*2+1] = BYTE((buf[x*2+1] * baUV + au) >> 9);
530
531 const int av = v_row[x] + v_row[x + 1];
532 buf[x*2+3] = BYTE((buf[x*2+3] * baUV + av) >> 9);
533 }
534 }
535 buf += pitch;
536 row_ptr += 4 * w_stride;
537 }
538 }
539
540
541 template<typename pixel_t, bool has_alpha>
542 void Antialiaser::ApplyRGB_packed(BYTE* buf, int pitch)
543 {
544 if (dirty) GetAlphaRect();
545 const uint16_t* row_ptr = soa_buf + yb * 4 * w_stride;
546 buf += pitch * (h - yb - 1); // packed RGB is stored bottom-up
547
548 constexpr int pixel_step = has_alpha ? 4 : 3;
549 constexpr int alpha_shift = sizeof(pixel_t) == 1 ? 0 : 8;
550 for (int y = yb; y <= yt; ++y) {
551 const uint16_t* ba_row = row_ptr;
552 const uint16_t* ry_row = row_ptr + w_stride; // R addend
553 const uint16_t* gu_row = row_ptr + 2 * w_stride; // G addend
554 const uint16_t* bv_row = row_ptr + 3 * w_stride; // B addend
555 for (int x = xl; x <= xr; ++x) {
556 const int ba = ba_row[x];
557 if (ba != 256) {
558 pixel_t* buf2 = reinterpret_cast<pixel_t*>(buf) + (x * pixel_step);
559 buf2[0] = (pixel_t)((buf2[0] * ba + (bv_row[x] << alpha_shift)) >> 8); // B
560 buf2[1] = (pixel_t)((buf2[1] * ba + (gu_row[x] << alpha_shift)) >> 8); // G
561 buf2[2] = (pixel_t)((buf2[2] * ba + (ry_row[x] << alpha_shift)) >> 8); // R
562 }
563 }
564 buf -= pitch; // packed RGB is bottom-up
565 row_ptr += 4 * w_stride;
566 }
567 }
568
569
570 void Antialiaser::GetAlphaRect()
571 {
572 dirty = false;
573 const int srcpitch = (w + 4 + 3) & -4;
574 #ifdef INTEL_INTRINSICS
575 if (getalpharect_fn) {
576 getalpharect_fn(lpAntialiasBits, soa_buf, w, h, w_stride, srcpitch,
577 textcolor, halocolor, xl, yt, xr, yb);
578 return;
579 }
580 #endif
581 GetAlphaRect_select_scalar(noaa, interlaced)(
582 lpAntialiasBits, soa_buf, w, h, w_stride, srcpitch,
583 textcolor, halocolor, xl, yt, xr, yb);
584 }
585
586 #endif // #if defined(AVS_WINDOWS) && !defined(NO_WIN_GDI)
587
588
589
590 /*************************************
591 ******* Show Frame Number ******
592 ************************************/
593
594 ShowFrameNumber::ShowFrameNumber(PClip _child, bool _scroll, int _offset, int _x, int _y, const char _fontname[],
595 int _size, int _textcolor, int _halocolor, int font_width, int font_angle,
596 bool _bold, bool _italic, bool _noaa, bool _gdi, IScriptEnvironment* env)
597 : GenericVideoFilter(_child),
598 #if defined(AVS_WINDOWS) && !defined(NO_WIN_GDI)
599 use_gdi(_gdi),
600 antialiaser(_gdi ? std::make_unique<Antialiaser>(vi.width, vi.height, _fontname, _size,
601 vi.IsYUV() || vi.IsYUVA() ? RGB2YUV_Rec601(_textcolor) : _textcolor,
602 vi.IsYUV() || vi.IsYUVA() ? RGB2YUV_Rec601(_halocolor) : _halocolor,
603 _bold, _italic, _noaa,
604 env->GetCPUFlagsEx(), ChromaLocation_e::AVS_CHROMA_CENTER /*center is quick*/,
605 font_width, font_angle, false) : nullptr),
606 #else
607 use_gdi(false),
608 #endif
609 scroll(_scroll), offset(_offset), size(_size), x(_x), y(_y),
610 textcolor(vi.IsYUV() || vi.IsYUVA() ? RGB2YUV_Rec601(_textcolor) : _textcolor),
611 halocolor(vi.IsYUV() || vi.IsYUVA() ? RGB2YUV_Rec601(_halocolor) : _halocolor),
612 bold(_bold), italic(_italic), noaa(_noaa)
613 {
614 chromaplacement = ChromaLocation_e::AVS_CHROMA_LEFT;
615 AVS_UNUSED(env);
616
617 if (!use_gdi) {
618 current_font = GetBitmapFont(size, "Terminus", bold, false);
619 if (current_font == nullptr) {
620 current_font = GetBitmapFont(size, "", bold, false);
621 if (current_font == nullptr)
622 current_font = GetBitmapFont(size, "", !bold, false);
623 }
624 }
625 }
626
627 enum { DefXY = (int)0x80000000 };
628
629 PVideoFrame ShowFrameNumber::GetFrame(int n, IScriptEnvironment* env) {
630 PVideoFrame frame = child->GetFrame(n, env);
631 n+=offset;
632 if (n < 0) return frame;
633
634 char text[16];
635 snprintf(text, sizeof(text), "%05d", n);
636 text[15] = 0;
637
638 #if defined(AVS_WINDOWS) && !defined(NO_WIN_GDI)
639 if (use_gdi) {
640 HDC hdc = antialiaser->GetDC();
641 if (!hdc) return frame;
642 env->MakeWritable(&frame);
643 RECT r = { 0, 0, 32767, 32767 };
644 FillRect(hdc, &r, (HBRUSH)GetStockObject(BLACK_BRUSH));
645 if (x!=DefXY || y!=DefXY) {
646 SetTextAlign(hdc, TA_BASELINE|TA_LEFT);
647 TextOut(hdc, x+16, y+16, text, (int)strlen(text));
648 } else if (scroll) {
649 int n1 = vi.IsFieldBased() ? (n/2) : n;
650 int y2 = size + size * (n1 % (vi.height * 8 / size));
651 SetTextAlign(hdc, TA_BASELINE | (child->GetParity(n) ? TA_LEFT : TA_RIGHT));
652 TextOut(hdc, child->GetParity(n) ? 32 : vi.width*8+8, y2, text, (int)strlen(text));
653 } else {
654 SetTextAlign(hdc, TA_BASELINE | (child->GetParity(n) ? TA_LEFT : TA_RIGHT));
655 int text_len = (int)strlen(text);
656 for (int y2 = size; y2 < vi.height * 8; y2 += size)
657 TextOut(hdc, child->GetParity(n) ? 32 : vi.width * 8 + 8, y2, text, text_len);
658 }
659 GdiFlush();
660 antialiaser->Apply(vi, &frame, frame->GetPitch());
661 return frame;
662 }
663 #endif
664 if (current_font == nullptr)
665 return frame;
666 env->MakeWritable(&frame);
667 std::string s_utf8 = charToUtf8(text, true);
668 if (x!=DefXY || y!=DefXY) {
669 SimpleTextOutW(current_font.get(), vi, frame, x, y, s_utf8, false, textcolor, halocolor, true, 1, chromaplacement);
670 } else if (scroll) {
671 int n1 = vi.IsFieldBased() ? (n/2) : n;
672 int y2 = size + size * (n1 % (vi.height / size));
673 if(child->GetParity(n))
674 SimpleTextOutW(current_font.get(), vi, frame, 4, y2, s_utf8, false, textcolor, halocolor, true, 1, chromaplacement); // left
675 else
676 SimpleTextOutW(current_font.get(), vi, frame, vi.width - 1, y2, s_utf8, false, textcolor, halocolor, true, 3, chromaplacement); // right
677 } else {
678 // size-1 because of bottom alignment
679 for (int y2 = size - 1; y2 < vi.height; y2 += size) {
680 if (child->GetParity(n))
681 SimpleTextOutW(current_font.get(), vi, frame, 4, y2, s_utf8, false, textcolor, halocolor, true, 1, chromaplacement); // bottom-left
682 else
683 SimpleTextOutW(current_font.get(), vi, frame, vi.width - 1, y2, s_utf8, false, textcolor, halocolor, true, 3, chromaplacement); // bottom-right
684 }
685 }
686 return frame;
687 }
688
689
690 AVSValue __cdecl ShowFrameNumber::Create(AVSValue args, void*, IScriptEnvironment* env)
691 {
692 PClip clip = args[0].AsClip();
693 bool scroll = args[1].AsBool(false);
694 const int offset = args[2].AsInt(0);
695 #if defined(AVS_WINDOWS) && !defined(NO_WIN_GDI)
696 const bool gdi = args[14].AsBool(true);
697 const int x = args[3].IsFloat() ? int(args[3].AsFloat() * (gdi ? 8 : 1) + 0.5) : DefXY;
698 const int y = args[4].IsFloat() ? int(args[4].AsFloat() * (gdi ? 8 : 1) + 0.5) : DefXY;
699 const char* font = args[5].AsString(gdi ? "Arial" : "Terminus");
700 const int size = int(args[6].AsFloat(24) * (gdi ? 8 : 1) + 0.5);
701 const int font_width = int(args[9].AsFloat(0) * (gdi ? 8 : 1) + 0.5);
702 const bool bold = args[11].AsBool(gdi);
703 #else
704 const bool gdi = false;
705 const int x = args[3].IsFloat() ? int(args[3].AsFloat() + 0.5) : DefXY;
706 const int y = args[4].IsFloat() ? int(args[4].AsFloat() + 0.5) : DefXY;
707 const char* font = args[5].AsString("Terminus");
708 const int size = int(args[6].AsFloat(24) + 0.5);
709 const int font_width = int(args[9].AsFloat(0) + 0.5);
710 const bool bold = args[11].AsBool(false);
711 #endif
712 const int text_color = args[7].AsInt(0xFFFF00);
713 const int halo_color = args[8].AsInt(0);
714 const int font_angle = int(args[10].AsFloat(0) * 10 + 0.5);
715 const bool italic = args[12].AsBool(false);
716 const bool noaa = args[13].AsBool(false);
717
718 if ((x==DefXY) ^ (y==DefXY))
719 env->ThrowError("ShowFrameNumber: both x and y position must be specified");
720
721 return new ShowFrameNumber(clip, scroll, offset, x, y, font, size, text_color, halo_color, font_width, font_angle, bold, italic, noaa, gdi, env);
722 }
723
724
725
726
727
728 /*************************************
729 ******* Show CRC32 Number ******
730 ************************************/
731
732 ShowCRC32::ShowCRC32(PClip _child, PClip _crc_child, bool _scroll, int _offset, int _x, int _y,
733 const char _fontname[], int _size, int _textcolor, int _halocolor, int font_width, int font_angle,
734 bool _bold, bool _italic, bool _noaa, bool _gdi,
735 const char* channels, int _mode, bool _compatible_mode, int _showmode, IScriptEnvironment* env)
736 : GenericVideoFilter(_child),
737 #if defined(AVS_WINDOWS) && !defined(NO_WIN_GDI)
738 use_gdi(_gdi),
739 antialiaser(_gdi ? std::make_unique<Antialiaser>(vi.width, vi.height, _fontname, _size,
740 vi.IsYUV() || vi.IsYUVA() ? RGB2YUV_Rec601(_textcolor) : _textcolor,
741 vi.IsYUV() || vi.IsYUVA() ? RGB2YUV_Rec601(_halocolor) : _halocolor,
742 _bold, _italic, _noaa,
743 env->GetCPUFlagsEx(), ChromaLocation_e::AVS_CHROMA_CENTER /*center is quick*/,
744 font_width, font_angle, false) : nullptr),
745 #else
746 use_gdi(false),
747 #endif
748 crc_child(_crc_child),
749 mode(_mode),
750 compatible_mode(_compatible_mode),
751 showmode(_showmode),
752 scroll(_scroll), offset(_offset), size(_size), x(_x), y(_y),
753 textcolor(vi.IsYUV() || vi.IsYUVA() ? RGB2YUV_Rec601(_textcolor) : _textcolor),
754 halocolor(vi.IsYUV() || vi.IsYUVA() ? RGB2YUV_Rec601(_halocolor) : _halocolor),
755 bold(_bold), italic(_italic), noaa(_noaa)
756 {
757 AVS_UNUSED(env);
758
759 doB = doG = doR = doA = doY = doU = doV = false;
760 for (int k = 0; channels[k] != '\0'; ++k) {
761 switch (channels[k]) {
762 case 'B': case 'b': doB = true; break;
763 case 'G': case 'g': doG = true; break;
764 case 'R': case 'r': doR = true; break;
765 case 'A': case 'a': doA = true; break;
766 case 'Y': case 'y': doY = true; break;
767 case 'U': case 'u': doU = true; break;
768 case 'V': case 'v': doV = true; break;
769 default: break;
770 }
771 }
772
773 build_crc32_table();
774
775 if (!use_gdi) {
776 current_font = GetBitmapFont(size, "Terminus", bold, false);
777 chromaplacement = ChromaLocation_e::AVS_CHROMA_LEFT;
778 if (current_font == nullptr) {
779 current_font = GetBitmapFont(size, "", bold, false);
780 if (current_font == nullptr)
781 current_font = GetBitmapFont(size, "", !bold, false);
782 }
783 }
784 }
785
786 void ShowCRC32::build_crc32_table(void) {
787 for (uint32_t i = 0; i < 256; i++) {
788 uint32_t ch = i;
789 uint32_t crc = 0;
790 for (size_t j = 0; j < 8; j++) {
791 uint32_t b = (ch ^ crc) & 1;
792 crc >>= 1;
793 if (b) crc = crc ^ 0xEDB88320;
794 ch >>= 1;
795 }
796 crc32_table[i] = crc;
797 }
798 }
799
800 std::string ShowCRC32::compute_crc_text(PVideoFrame& crc_frame, std::vector<uint32_t>& out_values) const {
801 out_values.clear();
802 char buf[16];
803
804 if (compatible_mode) {
805 // Packed format, default parameters: hash the raw interleaved buffer directly.
806 const uint8_t* ptr = crc_frame->GetReadPtr();
807 int rowsize = crc_frame->GetRowSize();
808 int pitch = crc_frame->GetPitch();
809 int height = crc_frame->GetHeight();
810 uint32_t crc = 0xFFFFFFFF;
811 for (int y = 0; y < height; y++) {
812 for (int x = 0; x < rowsize; x++) {
813 uint32_t t = (ptr[x] ^ crc) & 0xFF;
814 crc = (crc >> 8) ^ crc32_table[t];
815 }
816 ptr += pitch;
817 }
818 crc = ~crc;
819 out_values.push_back(crc);
820 snprintf(buf, sizeof(buf), "%08X", (unsigned int)crc);
821 return buf;
822 }
823
824 const VideoInfo& crc_vi = crc_child->GetVideoInfo();
825
826 struct PlaneEntry { int id; const char* label; bool active; };
827 PlaneEntry entries[4];
828 int nentries = 0;
829
830 if (crc_vi.IsYUV() || crc_vi.IsYUVA()) {
831 entries[nentries++] = { PLANAR_Y, "Y", doY };
832 if (crc_vi.NumComponents() > 1) {
833 entries[nentries++] = { PLANAR_U, "U", doU };
834 entries[nentries++] = { PLANAR_V, "V", doV };
835 }
836 if (crc_vi.IsYUVA())
837 entries[nentries++] = { PLANAR_A, "A", doA };
838 } else { // PlanarRGB(A) — displayed in logical R,G,B,A order regardless of physical plane order
839 entries[nentries++] = { PLANAR_R, "R", doR };
840 entries[nentries++] = { PLANAR_G, "G", doG };
841 entries[nentries++] = { PLANAR_B, "B", doB };
842 if (crc_vi.IsPlanarRGBA())
843 entries[nentries++] = { PLANAR_A, "A", doA };
844 }
845
846 if (mode == 0) {
847 // Combined CRC over all selected planes in order
848 uint32_t crc = 0xFFFFFFFF;
849 for (int i = 0; i < nentries; i++) {
850 if (!entries[i].active) continue;
851 const uint8_t* ptr = crc_frame->GetReadPtr(entries[i].id);
852 int rowsize = crc_frame->GetRowSize(entries[i].id);
853 int pitch = crc_frame->GetPitch(entries[i].id);
854 int height = crc_frame->GetHeight(entries[i].id);
855 for (int y = 0; y < height; y++) {
856 for (int x = 0; x < rowsize; x++) {
857 uint32_t t = (ptr[x] ^ crc) & 0xFF;
858 crc = (crc >> 8) ^ crc32_table[t];
859 }
860 ptr += pitch;
861 }
862 }
863 crc = ~crc;
864 out_values.push_back(crc);
865 snprintf(buf, sizeof(buf), "%08X", (unsigned int)crc);
866 return buf;
867 } else {
868 // Separate CRC per plane, displayed as "Y:XXXXXXXX U:XXXXXXXX ..."
869 std::string result;
870 for (int i = 0; i < nentries; i++) {
871 if (!entries[i].active) continue;
872 const uint8_t* ptr = crc_frame->GetReadPtr(entries[i].id);
873 int rowsize = crc_frame->GetRowSize(entries[i].id);
874 int pitch = crc_frame->GetPitch(entries[i].id);
875 int height = crc_frame->GetHeight(entries[i].id);
876 uint32_t crc = 0xFFFFFFFF;
877 for (int y = 0; y < height; y++) {
878 for (int x = 0; x < rowsize; x++) {
879 uint32_t t = (ptr[x] ^ crc) & 0xFF;
880 crc = (crc >> 8) ^ crc32_table[t];
881 }
882 ptr += pitch;
883 }
884 crc = ~crc;
885 out_values.push_back(crc);
886 if (!result.empty()) result += ' ';
887 snprintf(buf, sizeof(buf), "%s:%08X", entries[i].label, (unsigned int)crc);
888 result += buf;
889 }
890 return result;
891 }
892 }
893
894
895 PVideoFrame ShowCRC32::GetFrame(int n, IScriptEnvironment* env) {
896 PVideoFrame frame = child->GetFrame(n, env);
897 PVideoFrame crc_frame = crc_child->GetFrame(n, env);
898 n += offset;
899 if (n < 0) return frame;
900
901 std::vector<uint32_t> crc_vals;
902 std::string crc_str = compute_crc_text(crc_frame, crc_vals);
903
904 if (showmode >= 1) {
905 env->MakePropertyWritable(&frame);
906 AVSMap* avsmap = env->getFramePropsRW(frame);
907 std::vector<int64_t> int64array(crc_vals.size());
908 for (size_t i = 0; i < crc_vals.size(); i++)
909 int64array[i] = static_cast<int64_t>(crc_vals[i]); // uint32 fits non-negative in int64
910 env->propSetIntArray(avsmap, "ShowCRC32", int64array.data(), (int)int64array.size());
911 }
912
913 if (showmode == 2)
914 return frame;
915
916 const char* text = crc_str.c_str();
917
918 #if defined(AVS_WINDOWS) && !defined(NO_WIN_GDI)
919 if (use_gdi) {
920 HDC hdc = antialiaser->GetDC();
921 if (!hdc) return frame;
922 env->MakeWritable(&frame);
923 RECT r = { 0, 0, 32767, 32767 };
924 FillRect(hdc, &r, (HBRUSH)GetStockObject(BLACK_BRUSH));
925 if (x != DefXY || y != DefXY) {
926 SetTextAlign(hdc, TA_BASELINE | TA_LEFT);
927 TextOut(hdc, x + 16, y + 16, text, (int)strlen(text));
928 } else if (scroll) {
929 int n1 = vi.IsFieldBased() ? (n / 2) : n;
930 int y2 = size + size * (n1 % (vi.height * 8 / size));
931 SetTextAlign(hdc, TA_BASELINE | (child->GetParity(n) ? TA_LEFT : TA_RIGHT));
932 TextOut(hdc, child->GetParity(n) ? 32 : vi.width * 8 + 8, y2, text, (int)strlen(text));
933 } else {
934 SetTextAlign(hdc, TA_BASELINE | (child->GetParity(n) ? TA_LEFT : TA_RIGHT));
935 int text_len = (int)strlen(text);
936 for (int y2 = size; y2 < vi.height * 8; y2 += size)
937 TextOut(hdc, child->GetParity(n) ? 32 : vi.width * 8 + 8, y2, text, text_len);
938 }
939 GdiFlush();
940 antialiaser->Apply(vi, &frame, frame->GetPitch());
941 return frame;
942 }
943 #endif
944 if (current_font == nullptr)
945 return frame;
946 env->MakeWritable(&frame);
947 std::string s_utf8 = charToUtf8(text, true);
948 if (x != DefXY || y != DefXY) {
949 SimpleTextOutW(current_font.get(), vi, frame, x, y, s_utf8, false, textcolor, halocolor, true, 1, chromaplacement);
950 } else if (scroll) {
951 int n1 = vi.IsFieldBased() ? (n / 2) : n;
952 int y2 = size + size * (n1 % (vi.height / size));
953 if (child->GetParity(n))
954 SimpleTextOutW(current_font.get(), vi, frame, 4, y2, s_utf8, false, textcolor, halocolor, true, 1, chromaplacement); // left
955 else
956 SimpleTextOutW(current_font.get(), vi, frame, vi.width - 1, y2, s_utf8, false, textcolor, halocolor, true, 3, chromaplacement); // right
957 } else {
958 for (int y2 = size; y2 < vi.height; y2 += size) {
959 if (child->GetParity(n))
960 SimpleTextOutW(current_font.get(), vi, frame, 4, y2, s_utf8, false, textcolor, halocolor, true, 1, chromaplacement); // left
961 else
962 SimpleTextOutW(current_font.get(), vi, frame, vi.width - 1, y2, s_utf8, false, textcolor, halocolor, true, 3, chromaplacement); // right
963 }
964 }
965 return frame;
966 }
967
968
969 AVSValue __cdecl ShowCRC32::Create(AVSValue args, void*, IScriptEnvironment* env)
970 {
971 PClip original = args[0].AsClip();
972 const VideoInfo& vi = original->GetVideoInfo();
973
974 // Compatible mode: packed format, no explicit channels/mode — hash raw interleaved buffer.
975 const bool is_packed = vi.IsYUY2() || vi.IsRGB24() || vi.IsRGB32() || vi.IsRGB48() || vi.IsRGB64();
976 const bool compatible_mode = is_packed && !args[14].Defined() && !args[15].Defined();
977
978 // Build a planar CRC child so per-plane access works for packed formats.
979 PClip crc_child;
980 if (!compatible_mode && vi.IsYUY2()) {
981 AVSValue a[1] = { original };
982 crc_child = env->Invoke("ConvertToYV16", AVSValue(a, 1)).AsClip();
983 } else if (!compatible_mode && (vi.IsRGB32() || vi.IsRGB64())) {
984 AVSValue a[1] = { original };
985 crc_child = env->Invoke("ConvertToPlanarRGBA", AVSValue(a, 1)).AsClip();
986 } else if (!compatible_mode && (vi.IsRGB24() || vi.IsRGB48())) {
987 AVSValue a[1] = { original };
988 crc_child = env->Invoke("ConvertToPlanarRGB", AVSValue(a, 1)).AsClip();
989 } else {
990 crc_child = original;
991 }
992
993 const VideoInfo& crc_vi = crc_child->GetVideoInfo();
994 const char* default_channels = crc_vi.IsRGB() ? "RGBA" : "YUVA";
995 const char* channels = args[14].AsString(default_channels);
996 const int mode = args[15].AsInt(0);
997 const int showmode = args[16].AsInt(0);
998 if (showmode < 0 || showmode > 2)
999 env->ThrowError("ShowCRC32: showmode must be 0, 1 or 2");
1000
1001 #if defined(AVS_WINDOWS) && !defined(NO_WIN_GDI)
1002 const bool gdi = args[17].AsBool(true);
1003 const int x = args[3].IsFloat() ? int(args[3].AsFloat() * (gdi ? 8 : 1) + 0.5) : DefXY;
1004 const int y = args[4].IsFloat() ? int(args[4].AsFloat() * (gdi ? 8 : 1) + 0.5) : DefXY;
1005 const char* font = args[5].AsString(gdi ? "Arial" : "Terminus");
1006 const int size = int(args[6].AsFloat(24) * (gdi ? 8 : 1) + 0.5);
1007 const int font_width = int(args[9].AsFloat(0) * (gdi ? 8 : 1) + 0.5);
1008 const bool bold = args[11].AsBool(gdi);
1009 #else
1010 const bool gdi = false;
1011 const int x = args[3].IsFloat() ? int(args[3].AsFloat() + 0.5) : DefXY;
1012 const int y = args[4].IsFloat() ? int(args[4].AsFloat() + 0.5) : DefXY;
1013 const char* font = args[5].AsString("Terminus");
1014 const int size = int(args[6].AsFloat(24) + 0.5);
1015 const int font_width = int(args[9].AsFloat(0) + 0.5);
1016 const bool bold = args[11].AsBool(false);
1017 #endif
1018 const int text_color = args[7].AsInt(0xFFFF00);
1019 const int halo_color = args[8].AsInt(0);
1020 const int font_angle = int(args[10].AsFloat(0) * 10 + 0.5);
1021 const bool italic = args[12].AsBool(false);
1022 const bool noaa = args[13].AsBool(false);
1023
1024 if ((x == DefXY) ^ (y == DefXY))
1025 env->ThrowError("ShowCRC32: both x and y position must be specified");
1026
1027 return new ShowCRC32(original, crc_child,
1028 args[1].AsBool(false), args[2].AsInt(0),
1029 x, y, font, size, text_color, halo_color, font_width, font_angle,
1030 bold, italic, noaa, gdi, channels, mode, compatible_mode, showmode, env);
1031 }
1032
1033
1034
1035
1036
1037
1038
1039 /***********************************
1040 ******* Show SMPTE code ******
1041 **********************************/
1042
1043 ShowSMPTE::ShowSMPTE(PClip _child, double _rate, const char* offset, int _offset_f, int _x, int _y, const char _fontname[],
1044 int _size, int _textcolor, int _halocolor, int font_width, int font_angle, bool _bold, bool _italic, bool _noaa, bool _gdi, IScriptEnvironment* env)
1045 : GenericVideoFilter(_child),
1046 #if defined(AVS_WINDOWS) && !defined(NO_WIN_GDI)
1047 use_gdi(_gdi),
1048 antialiaser(_gdi ? std::make_unique<Antialiaser>(vi.width, vi.height, _fontname, _size,
1049 vi.IsYUV() || vi.IsYUVA() ? RGB2YUV_Rec601(_textcolor) : _textcolor,
1050 vi.IsYUV() || vi.IsYUVA() ? RGB2YUV_Rec601(_halocolor) : _halocolor,
1051 _bold, _italic, _noaa,
1052 env->GetCPUFlagsEx(), ChromaLocation_e::AVS_CHROMA_CENTER /*center is quick*/,
1053 font_width, font_angle, false) : nullptr),
1054 #else
1055 use_gdi(false),
1056 #endif
1057 x(_x), y(_y),
1058 textcolor(vi.IsYUV() || vi.IsYUVA() ? RGB2YUV_Rec601(_textcolor) : _textcolor),
1059 halocolor(vi.IsYUV() || vi.IsYUVA() ? RGB2YUV_Rec601(_halocolor) : _halocolor),
1060 bold(_bold), italic(_italic), noaa(_noaa)
1061 {
1062 if (!use_gdi) {
1063 current_font = GetBitmapFont(_size, "Terminus", bold, false);
1064 chromaplacement = ChromaLocation_e::AVS_CHROMA_LEFT;
1065 if (current_font == nullptr) {
1066 current_font = GetBitmapFont(_size, "", bold, false);
1067 if (current_font == nullptr)
1068 current_font = GetBitmapFont(_size, "", !bold, false);
1069 }
1070 }
1071 int off_f, off_sec, off_min, off_hour;
1072
1073 rate = int(_rate + 0.5);
1074 dropframe = false;
1075 if (_rate > 23.975 && _rate < 23.977) { // Pulldown drop frame rate
1076 rate = 24;
1077 dropframe = true;
1078 }
1079 else if (_rate > 29.969 && _rate < 29.971) {
1080 rate = 30;
1081 dropframe = true;
1082 }
1083 else if (_rate > 47.951 && _rate < 47.953) {
1084 rate = 48;
1085 dropframe = true;
1086 }
1087 else if (_rate > 59.939 && _rate < 59.941) {
1088 rate = 60;
1089 dropframe = true;
1090 }
1091 else if (_rate > 119.879 && _rate < 119.881) {
1092 rate = 120;
1093 dropframe = true;
1094 }
1095 else if (fabs(_rate - rate) > 0.001) {
1096 env->ThrowError("ShowSMPTE: rate argument must be 23.976, 29.97 or an integer");
1097 }
1098
1099 if (offset) {
1100 if (strlen(offset)!=11 || offset[2] != ':' || offset[5] != ':' || offset[8] != ':')
1101 env->ThrowError("ShowSMPTE: offset should be of the form \"00:00:00:00\" ");
1102 if (!isdigit(offset[0]) || !isdigit(offset[1]) || !isdigit(offset[3]) || !isdigit(offset[4])
1103 || !isdigit(offset[6]) || !isdigit(offset[7]) || !isdigit(offset[9]) || !isdigit(offset[10]))
1104 env->ThrowError("ShowSMPTE: offset should be of the form \"00:00:00:00\" ");
1105
1106 off_hour = atoi(offset);
1107
1108 off_min = atoi(offset+3);
1109 if (off_min > 59)
1110 env->ThrowError("ShowSMPTE: make sure that the number of minutes in the offset is in the range 0..59");
1111
1112 off_sec = atoi(offset+6);
1113 if (off_sec > 59)
1114 env->ThrowError("ShowSMPTE: make sure that the number of seconds in the offset is in the range 0..59");
1115
1116 off_f = atoi(offset+9);
1117 if (off_f >= rate)
1118 env->ThrowError("ShowSMPTE: make sure that the number of frames in the offset is in the range 0..%d", rate-1);
1119
1120 offset_f = off_f + rate*(off_sec + 60*off_min + 3600*off_hour);
1121 if (dropframe) {
1122 if (rate == 30) {
1123 int c = 0;
1124 c = off_min + 60*off_hour; // number of drop events
1125 c -= c/10; // less non-drop events on 10 minutes
1126 c *=2; // drop 2 frames per drop event
1127 offset_f -= c;
1128 }
1129 else {
1130 // Need to cogitate with the guys about this
1131 // gotta drop 86.3 counts per hour. So until
1132 // a proper formula is found, just wing it!
1133 offset_f -= 2 * ((offset_f+1001)/2002);
1134 }
1135 }
1136 }
1137 else {
1138 offset_f = _offset_f;
1139 }
1140 }
1141
1142
1143 PVideoFrame __stdcall ShowSMPTE::GetFrame(int n, IScriptEnvironment* env)
1144 {
1145 PVideoFrame frame = child->GetFrame(n, env);
1146 n+=offset_f;
1147 if (n < 0) return frame;
1148
1149 if (dropframe) {
1150 if ((rate == 30) || (rate == 60) || (rate == 120)) {
1151 // at 10:00, 20:00, 30:00, etc. nothing should happen if offset=0
1152 const int f = rate/30;
1153 const int r = n % f;
1154 n /= f;
1155
1156 const int high = n / 17982;
1157 int low = n % 17982;
1158 if (low>=2)
1159 low += 2 * ((low-2) / 1798);
1160 n = high * 18000 + low;
1161
1162 n = f*n + r;
1163 }
1164 else {
1165 // Needs some cogitating
1166 n += 2 * ((n+1001)/2002);
1167 }
1168 }
1169
1170 char text[32];
1171
1172 if (rate > 0) {
1173 int frames = n % rate;
1174 int sec = n/rate;
1175 int min = sec/60;
1176 int hour = sec/3600;
1177
1178 snprintf(text, sizeof(text),
1179 rate>99 ? "%02d:%02d:%02d:%03d" : "%02d:%02d:%02d:%02d",
1180 hour, min%60, sec%60, frames);
1181 }
1182 else {
1183 int ms = (int)(((int64_t)n * vi.fps_denominator * 1000 / vi.fps_numerator)%1000);
1184 int sec = (int)((int64_t)n * vi.fps_denominator / vi.fps_numerator);
1185 int min = sec/60;
1186 int hour = sec/3600;
1187
1188 snprintf(text, sizeof(text), "%02d:%02d:%02d.%03d", hour, min%60, sec%60, ms);
1189 }
1190 text[15] = 0;
1191
1192 #if defined(AVS_WINDOWS) && !defined(NO_WIN_GDI)
1193 if (use_gdi) {
1194 HDC hdc = antialiaser->GetDC();
1195 if (!hdc) return frame;
1196 env->MakeWritable(&frame);
1197 SetTextAlign(hdc, TA_BASELINE|TA_CENTER);
1198 TextOut(hdc, x+16, y+16, text, (int)strlen(text));
1199 GdiFlush();
1200 antialiaser->Apply(vi, &frame, frame->GetPitch());
1201 return frame;
1202 }
1203 #endif
1204 if (current_font == nullptr)
1205 return frame;
1206 env->MakeWritable(&frame);
1207 const bool utf8 = true;
1208 auto s_utf8 = charToUtf8(text, utf8);
1209 SimpleTextOutW(current_font.get(), vi, frame, x + 2, y + 2, s_utf8, true, textcolor, halocolor, false, 2 /* V baseline H center */, chromaplacement);
1210 return frame;
1211 }
1212
1213 AVSValue __cdecl ShowSMPTE::CreateSMTPE(AVSValue args, void*, IScriptEnvironment* env)
1214 {
1215 PClip clip = args[0].AsClip();
1216 const VideoInfo& arg0vi = args[0].AsClip()->GetVideoInfo();
1217 double def_rate = (double)arg0vi.fps_numerator / arg0vi.fps_denominator;
1218 double dfrate = args[1].AsDblDef(def_rate);
1219 const char* offset = args[2].AsString(0);
1220 const int offset_f = args[3].AsInt(0);
1221 const int xreal = arg0vi.width/2;
1222 const int yreal = arg0vi.height-8;
1223 #if defined(AVS_WINDOWS) && !defined(NO_WIN_GDI)
1224 const bool gdi = args[15].AsBool(true);
1225 const int x = int(args[4].AsDblDef(xreal) * (gdi ? 8 : 1) + 0.5);
1226 const int y = int(args[5].AsDblDef(yreal) * (gdi ? 8 : 1) + 0.5);
1227 const char* font = args[6].AsString(gdi ? "Arial" : "Terminus");
1228 const int size = int(args[7].AsFloat(24) * (gdi ? 8 : 1) + 0.5);
1229 const int font_width = int(args[10].AsFloat(0) * (gdi ? 8 : 1) + 0.5);
1230 const bool bold = args[12].AsBool(gdi);
1231 #else
1232 const bool gdi = false;
1233 const int x = int(args[4].AsDblDef(xreal) + 0.5);
1234 const int y = int(args[5].AsDblDef(yreal) + 0.5);
1235 const char* font = args[6].AsString("Terminus");
1236 const int size = int(args[7].AsFloat(24) + 0.5);
1237 const int font_width = int(args[10].AsFloat(0) + 0.5);
1238 const bool bold = args[12].AsBool(false);
1239 #endif
1240 const int text_color = args[8].AsInt(0xFFFF00);
1241 const int halo_color = args[9].AsInt(0);
1242 const int font_angle = int(args[11].AsFloat(0)*10+0.5);
1243 const bool italic = args[13].AsBool(false);
1244 const bool noaa = args[14].AsBool(false);
1245
1246 return new ShowSMPTE(clip, dfrate, offset, offset_f, x, y, font, size, text_color, halo_color, font_width, font_angle, bold, italic, noaa, gdi, env);
1247 }
1248
1249 AVSValue __cdecl ShowSMPTE::CreateTime(AVSValue args, void*, IScriptEnvironment* env)
1250 {
1251 PClip clip = args[0].AsClip();
1252 const int offset_f = args[1].AsInt(0);
1253 const int xreal = args[0].AsClip()->GetVideoInfo().width/2;
1254 const int yreal = args[0].AsClip()->GetVideoInfo().height-8;
1255 #if defined(AVS_WINDOWS) && !defined(NO_WIN_GDI)
1256 const bool gdi = args[13].AsBool(true);
1257 const int x = int(args[2].AsDblDef(xreal) * (gdi ? 8 : 1) + 0.5);
1258 const int y = int(args[3].AsDblDef(yreal) * (gdi ? 8 : 1) + 0.5);
1259 const char* font = args[4].AsString(gdi ? "Arial" : "Terminus");
1260 const int size = int(args[5].AsFloat(24) * (gdi ? 8 : 1) + 0.5);
1261 const int font_width = int(args[8].AsFloat(0) * (gdi ? 8 : 1) + 0.5);
1262 const bool bold = args[10].AsBool(gdi);
1263 #else
1264 const bool gdi = false;
1265 const int x = int(args[2].AsDblDef(xreal) + 0.5);
1266 const int y = int(args[3].AsDblDef(yreal) + 0.5);
1267 const char* font = args[4].AsString("Terminus");
1268 const int size = int(args[5].AsFloat(24) + 0.5);
1269 const int font_width = int(args[8].AsFloat(0) + 0.5);
1270 const bool bold = args[10].AsBool(false);
1271 #endif
1272 const int text_color = args[6].AsInt(0xFFFF00);
1273 const int halo_color = args[7].AsInt(0);
1274 const int font_angle = int(args[9].AsFloat(0)*10+0.5);
1275 const bool italic = args[11].AsBool(false);
1276 const bool noaa = args[12].AsBool(false);
1277
1278 return new ShowSMPTE(clip, 0.0, NULL, offset_f, x, y, font, size, text_color, halo_color, font_width, font_angle, bold, italic, noaa, gdi, env);
1279 }
1280
1281
1282
1283
1284
1285 #if defined(AVS_WINDOWS) && !defined(NO_WIN_GDI)
1286
1287 /***********************************
1288 ******* Subtitle Filter ******
1289 **********************************/
1290
1291 Subtitle::Subtitle( PClip _child, const char _text[], int _x, int _y, int _firstframe,
1292 int _lastframe, const char _fontname[], int _size, int _textcolor,
1293 int _halocolor, int _align, int _spc, bool _multiline, int _lsp,
1294 int _font_width, int _font_angle, bool _interlaced, const char _font_filename[], const bool _utf8,
1295 const bool _bold, const bool _italic, const bool _noaa, int _chromaplacement, IScriptEnvironment* env)
1296 : GenericVideoFilter(_child),
1297 x(_x), y(_y),
1298 firstframe(_firstframe), lastframe(_lastframe), size(_size),
1299 lsp(_lsp), font_width(_font_width), font_angle(_font_angle), multiline(_multiline), interlaced(_interlaced),
1300 textcolor(vi.IsYUV() || vi.IsYUVA() ? RGB2YUV_Rec601(_textcolor) : _textcolor),
1301 halocolor(vi.IsYUV() || vi.IsYUVA() ? RGB2YUV_Rec601(_halocolor) : _halocolor),
1302 align(_align), spc(_spc),
1303 fontname(_fontname), text(_text), font_filename(_font_filename), utf8(_utf8),
1304 bold(_bold), italic(_italic), noaa(_noaa),
1305 // only three types supported
1306 chromaplacement(
1307 _chromaplacement == ChromaLocation_e::AVS_CHROMA_CENTER ||
1308 _chromaplacement == ChromaLocation_e::AVS_CHROMA_LEFT ||
1309 _chromaplacement == ChromaLocation_e::AVS_CHROMA_TOP_LEFT ? _chromaplacement : ChromaLocation_e::AVS_CHROMA_LEFT),
1310 antialiaser(nullptr)
1311 {
1312 if (*font_filename) {
1313 int added_font_count = AddFontResourceEx(
1314 font_filename, // font file name
1315 FR_PRIVATE, // font characteristics
1316 NULL);
1317 if (added_font_count == 0)
1318 env->ThrowError("SubTitle: font %s not found", font_filename);
1319 }
1320 }
1321
1322
1323
1324 Subtitle::~Subtitle(void)
1325 {
1326 delete antialiaser;
1327 if (font_filename) {
1328 RemoveFontResourceEx(
1329 font_filename, // name of font file
1330 FR_PRIVATE, // font characteristics
1331 NULL // Reserved.
1332 );
1333 }
1334 }
1335
1336
1337
1338 PVideoFrame Subtitle::GetFrame(int n, IScriptEnvironment* env)
1339 {
1340 PVideoFrame frame = child->GetFrame(n, env);
1341
1342 if (n >= firstframe && n <= lastframe) {
1343 env->MakeWritable(&frame);
1344 if (!antialiaser) // :FIXME: CriticalSection
1345 InitAntialiaser(env);
1346 if (antialiaser) {
1347 antialiaser->Apply(vi, &frame, frame->GetPitch());
1348 // Release all the windows drawing stuff
1349 // and just keep the alpha calcs
1350 antialiaser->FreeDC();
1351 }
1352 }
1353 // if we get far enough away from the frames we're supposed to
1354 // subtitle, then junk the buffered drawing information
1355 if (antialiaser && (n < firstframe-10 || n > lastframe+10 || n == vi.num_frames-1)) {
1356 delete antialiaser;
1357 antialiaser = 0; // :FIXME: CriticalSection
1358 }
1359
1360 return frame;
1361 }
1362
1363 AVSValue __cdecl Subtitle::Create(AVSValue args, void*, IScriptEnvironment* env)
1364 {
1365 PClip clip = args[0].AsClip();
1366 const char* text = args[1].AsString();
1367 const int first_frame = args[4].AsInt(0);
1368 const int last_frame = args[5].AsInt(clip->GetVideoInfo().num_frames-1);
1369 const char* font = args[6].AsString("Arial");
1370 const int size = int(args[7].AsFloat(18)*8+0.5);
1371 const int text_color = args[8].AsInt(0xFFFF00);
1372 const int halo_color = args[9].AsInt(0);
1373 const int align = args[10].AsInt(args[2].AsFloat(0)==-1?2:7);
1374 const int spc = args[11].AsInt(0);
1375 const bool multiline = args[12].Defined();
1376 const int lsp = args[12].AsInt(0);
1377 const int font_width = int(args[13].AsFloat(0)*8+0.5);
1378 const int font_angle = int(args[14].AsFloat(0)*10+0.5);
1379 const bool interlaced = args[15].AsBool(false);
1380 const char* font_filename = args[16].AsString("");
1381 const bool utf8 = args[17].AsBool(false);
1382 const bool bold = args[18].AsBool(true);
1383 const bool italic = args[19].AsBool(false);
1384 const bool noaa = args[20].AsBool(false);
1385 const char* placement_name = args[21].AsString(nullptr);
1386 const bool gdi = args[22].AsBool(true);
1387
1388 if (!gdi)
1389 return SimpleText::Create(args, nullptr, env);
1390
1391 VideoInfo vi = clip->GetVideoInfo();
1392 int ChromaLocation_In = -1;
1393 if (vi.IsYV411()) {
1394 auto frame0 = clip->GetFrame(0, env);
1395 const AVSMap* props = env->getFramePropsRO(frame0);
1396 chromaloc_parse_merge_with_props(vi, placement_name, props, ChromaLocation_In, -1, env);
1397 }
1398 else if (vi.Is420() || vi.Is422() || vi.IsYUY2()) {
1399 auto frame0 = clip->GetFrame(0, env);
1400 const AVSMap* props = env->getFramePropsRO(frame0);
1401 chromaloc_parse_merge_with_props(vi, placement_name, props, ChromaLocation_In, ChromaLocation_e::AVS_CHROMA_LEFT, env);
1402 }
1403
1404 if ((align < 1) || (align > 9))
1405 env->ThrowError("Subtitle: Align values are 1 - 9 mapped to your numeric pad");
1406
1407 const int subtitle_default_x = 8;
1408
1409 int defx, defy;
1410 bool x_center = false;
1411 bool y_center = false;
1412
1413 switch (align) {
1414 case 1: case 4: case 7: defx = subtitle_default_x; break;
1415 case 2: case 5: case 8:
1416 defx = 0; // n/a if not set later
1417 x_center = true;
1418 break;
1419 case 3: case 6: case 9: defx = clip->GetVideoInfo().width - subtitle_default_x; break;
1420 default: defx = subtitle_default_x; break;
1421 }
1422
1423 switch (align) {
1424 case 1: case 2: case 3: defy = clip->GetVideoInfo().height - 2; break; // bottom alignment 2 pixel above
1425 case 4: case 5: case 6:
1426 defy = 0; // n/a if not set later
1427 y_center = true;
1428 break;
1429 case 7: case 8: case 9: defy = 0; break;
1430 default: defy = (size + 4) / 8; break;
1431 }
1432
1433 const bool isXdefined = args[2].Defined();
1434 const bool isYdefined = args[3].Defined();
1435
1436 int x = int(args[2].AsDblDef(defx)*8+0.5);
1437 int y = int(args[3].AsDblDef(defy)*8+0.5);
1438
1439 if (!isXdefined && x_center)
1440 x = (clip->GetVideoInfo().width >> 1) * 8;
1441
1442 if (!isYdefined && y_center)
1443 y = (clip->GetVideoInfo().height >> 1) * 8;
1444
1445 return new Subtitle(clip, text, x, y, first_frame, last_frame, font, size, text_color,
1446 halo_color, align, spc, multiline, lsp, font_width, font_angle, interlaced, font_filename, utf8,
1447 bold, italic, noaa, ChromaLocation_In, env);
1448 }
1449
1450 // multiline separator: string contains '\' and 'n' characters explicitely
1451 // SubTitle compatibility: literal "\n" means line break, but "\\n" means that literal "\n" will be printed
1452 // Since 3.7.4 the C style LF '\n' or CR LF ('\r' and '\n') always results in new line
1453 static std::vector<std::string> SplitLines(const char* text, bool multiline_by_param) {
1454 std::string s = text;
1455 std::vector<std::string> lines;
1456 size_t start = 0;
1457 size_t length = s.length();
1458
1459 for (size_t i = 0; i < length; ++i) {
1460
1461 if (multiline_by_param && s[i] == '\\') {
1462 if (i + 1 < length && s[i + 1] == 'n') {
1463 if (i > 0 && s[i - 1] == '\\') {
1464 // "\\n" case: keep as literal "\n"
1465 i++;
1466 }
1467 else {
1468 // "\n" case: split the line
1469 lines.push_back(s.substr(start, i - start));
1470 start = i + 2;
1471 i++;
1472 }
1473 }
1474 }
1475 else if (s[i] == '\n') {
1476 // Usual line feed '\n' case: split the line
1477 lines.push_back(s.substr(start, i - start));
1478 start = i + 1;
1479 }
1480 else if (s[i] == '\r' && i + 1 < length && s[i + 1] == '\n') {
1481 // Windows-style CR LF case: split the line
1482 lines.push_back(s.substr(start, i - start));
1483 start = i + 2;
1484 i++;
1485 }
1486 }
1487
1488 // Add the last line if it's not empty
1489 if (start < length) {
1490 lines.push_back(s.substr(start));
1491 }
1492
1493 return lines;
1494 }
1495
1496 static int CorrectYbyTextAndAlignment(int real_y, int align, int fontsize, int lsp, size_t line_count)
1497 {
1498 // note: we do not really have a vertical center alignment for multiline strings since it means not centering.
1499 // TA_BASELINE is aligning (x,y) to the character's baseline: letters like g, y and q would reach below that baseline)
1500
1501 // when multiline, bottom and vertically centered cases affect starting y
1502 if (align == 1 || align == 2 || align == 3) // bottom
1503 real_y -= (fontsize + lsp) * ((int)line_count - 1);
1504 else if (align == 4 || align == 5 || align == 6)
1505 real_y -= ((fontsize + lsp) * ((int)line_count - 1) + 1) / 2;
1506 return real_y;
1507 }
1508
1509 void Subtitle::InitAntialiaser(IScriptEnvironment* env)
1510 {
1511 antialiaser = new Antialiaser(vi.width, vi.height, fontname, size, textcolor, halocolor, bold, italic, noaa,
1512 env->GetCPUFlagsEx(), chromaplacement,
1513 font_width, font_angle, interlaced);
1514
1515 int real_x = x;
1516 int real_y = y;
1517 unsigned int al = 0;
1518 std::vector<std::string> lines;
1519
1520 HDC hdcAntialias = antialiaser->GetDC();
1521 if (!hdcAntialias) goto GDIError;
1522
1523 switch (align) // This spec where [X, Y] is relative to the text (inverted logic)
1524 { case 1: al = TA_BOTTOM | TA_LEFT; break; // .----
1525 case 2: al = TA_BOTTOM | TA_CENTER; break; // --.--
1526 case 3: al = TA_BOTTOM | TA_RIGHT; break; // ----.
1527 case 4: al = TA_BASELINE | TA_LEFT; break; // .____
1528 case 5: al = TA_BASELINE | TA_CENTER; break; // __.__
1529 case 6: al = TA_BASELINE | TA_RIGHT; break; // ____.
1530 case 7: al = TA_TOP | TA_LEFT; break; // `----
1531 case 8: al = TA_TOP | TA_CENTER; break; // --`--
1532 case 9: al = TA_TOP | TA_RIGHT; break; // ----`
1533 default: al= TA_BASELINE | TA_LEFT; break; // .____
1534 }
1535 if (SetTextCharacterExtra(hdcAntialias, spc) == 0x80000000) goto GDIError;
1536 if (SetTextAlign(hdcAntialias, al) == GDI_ERROR) goto GDIError;
1537
1538 // multiline is filter parameter, true when lsp is given.
1539 // 3.7.4 real LF or CR LF breaks line unconditionally.
1540 lines = SplitLines(text, multiline);
1541 if (lines.size() == 0) return;
1542 real_y = CorrectYbyTextAndAlignment(real_y, align, size, lsp, lines.size()); // find literal \ n for LF, not real LF 0x0A
1543
1544 if (utf8) {
1545 // Test:
1546 // Title="Cherry blossom "+CHR($E6)+CHR($A1)+CHR($9C)+CHR($E3)+CHR($81)+CHR($AE)+CHR($E8)+CHR($8A)+CHR($B1)
1547 // SubTitle(Title, utf8 = true)
1548 int y_inc = real_y + 16;
1549 for(auto s : lines) {
1550 auto textw = Utf8ToWideChar(s.c_str());
1551 if (!TextOutW(hdcAntialias, real_x + 16, y_inc, textw.get(), (int)wcslen(textw.get())))
1552 goto GDIError;
1553 y_inc += size + lsp;
1554 }
1555
1556 }
1557 else {
1558 int y_inc = real_y + 16;
1559 for (auto s : lines) {
1560 if (!TextOut(hdcAntialias, real_x + 16, y_inc, s.c_str(), (int)s.length()))
1561 goto GDIError;
1562 y_inc += size + lsp;
1563 }
1564 }
1565 if (!GdiFlush()) goto GDIError;
1566 return;
1567
1568 GDIError:
1569 delete antialiaser;
1570 antialiaser = 0;
1571
1572 env->ThrowError("Subtitle: GDI or Insufficient Memory Error");
1573 }
1574
1575
1576
1577 #endif
1578
1579 static int CalcFontSizeForInfo(int w, int h, bool autolarge, int upper_limit, bool gdi)
1580 {
1581 // if frame is smaller than minimum then font size will be decreased proportionally
1582 // normalized to 640x480 @fontsize=15:
1583 // 14 lines @height=15 takes ~480/2 pixels (half vertical screens)
1584 // Info screen takes horizontally ~0.6 * 640 pixels at font size 15
1585
1586 #if defined(AVS_WINDOWS) && !defined(NO_WIN_GDI)
1587 const int min_font_size = gdi ? 1 : 12;
1588 const int max_font_size = gdi ? 255 : 32;
1589 const int reference_font_size = gdi ? 15 : 18;
1590 const int reference_font_width = gdi ? 8 : 10;
1591 #else
1592 AVS_UNUSED(gdi);
1593 const int min_font_size = 12;
1594 const int max_font_size = 32;
1595 const int reference_font_size = 18; // 16x8 or 18x10, latter is more visible
1596 const int reference_font_width = 10;
1597 #endif
1598 // knowing that a usual Info() is 45x15 characters, plus a margin
1599 const int reference_min_height = 15 * reference_font_size;
1600 const int reference_min_width = 48 * reference_font_width; // (45 + margin) characters
1601
1602 // 0..reference_min_width :decrease (as always in Avs)
1603 // reference_min_width ... 640 : constant size
1604 // 640+ : enlarge when autolarge is enabled or else constant size
1605
1606 int w_restricted_font_size;
1607 if (w < reference_min_width)
1608 w_restricted_font_size = (reference_font_size * w) / reference_min_width;
1609 else if (w >= 640 && autolarge)
1610 w_restricted_font_size = reference_font_size * w / 640;
1611 else
1612 w_restricted_font_size = reference_font_size;
1613
1614 int h_restricted_font_size;
1615 if (h < reference_min_height)
1616 h_restricted_font_size = (reference_font_size * h) / reference_min_height;
1617 else if (h >= 480 && autolarge)
1618 h_restricted_font_size = reference_font_size * h / 480;
1619 else
1620 h_restricted_font_size = reference_font_size;
1621
1622 int effective_font_size = (w_restricted_font_size < h_restricted_font_size) ? w_restricted_font_size : h_restricted_font_size;
1623
1624 effective_font_size = std::max(effective_font_size, min_font_size);
1625 if (upper_limit > 0)
1626 effective_font_size = std::min(effective_font_size, max_font_size);
1627
1628 #if defined(AVS_WINDOWS) && !defined(NO_WIN_GDI)
1629 return gdi ? effective_font_size * 8 : effective_font_size / 2 * 2;
1630 #else
1631 return effective_font_size / 2 * 2; // no odd sized fixed fonts atm.
1632 #endif
1633
1634 }
1635
1636 /***********************************
1637 ******* SimpleText Filter *****
1638 ***********************************/
1639
1640 SimpleText::SimpleText(PClip _child, const char _text[], int _x, int _y, int _firstframe,
1641 int _lastframe, const char _fontname[], int _size, int _textcolor,
1642 int _halocolor, int _align, int _spc, bool _multiline, int _lsp,
1643 int _font_width, int _font_angle, bool _interlaced, const char _font_filename[],
1644 const bool _utf8, const bool _bold, const int _chromalocation,
1645 IScriptEnvironment* env)
1646 : GenericVideoFilter(_child),
1647 x(_x), y(_y),
1648 firstframe(_firstframe), lastframe(_lastframe), size(_size), lsp(_lsp),
1649 multiline(_multiline),
1650 textcolor(vi.IsYUV() || vi.IsYUVA() ? RGB2YUV_Rec601(_textcolor) : _textcolor),
1651 halocolor(vi.IsYUV() || vi.IsYUVA() ? RGB2YUV_Rec601(_halocolor) : _halocolor), // not supported
1652 align(_align),
1653 halocolor_orig(_halocolor),
1654 fontname(_fontname),
1655 text(_text),
1656 // spc(_spc), font_width(_font_width), font_angle(_font_angle), interlaced(_interlaced),
1657 font_filename(_font_filename), utf8(_utf8),
1658 bold(_bold),
1659 chromalocation(_chromalocation)
1660 {
1661
1662 if (*font_filename) {
1663 // external font file
1664 bool debugSave = size < 0;
1665 current_font = GetBitmapFont(0, font_filename, false, debugSave); // 0: size n/a
1666 if (current_font == nullptr)
1667 env->ThrowError("SimpleText: file %s not found or unknown file format", font_filename);
1668 }
1669 else
1670 {
1671 // internal font
1672 if (fontname) {
1673 current_font = GetBitmapFont(size, fontname, bold, false); // 12, "Terminus"
1674 if (current_font == nullptr)
1675 env->ThrowError("SimpleText: internal font name %s in size %d not found", fontname, size);
1676 }
1677 else {
1678 // size
1679 current_font = GetBitmapFont(size, "", bold, false); // 12, ""
1680 if (current_font == nullptr)
1681 env->ThrowError("SimpleText: fixed font size %d not found", size);
1682 }
1683 }
1684 }
1685
1686
1687
1688 SimpleText::~SimpleText(void)
1689 {
1690 // nothing here yet
1691 }
1692
1693
1694 PVideoFrame SimpleText::GetFrame(int n, IScriptEnvironment* env)
1695 {
1696 PVideoFrame frame = child->GetFrame(n, env);
1697
1698 if (n >= firstframe && n <= lastframe) {
1699 env->MakeWritable(&frame);
1700
1701 int real_x = x;
1702 int real_y = y;
1703
1704 // Test:
1705 // Title="Cherry blossom "+CHR($E6)+CHR($A1)+CHR($9C)+CHR($E3)+CHR($81)+CHR($AE)+CHR($E8)+CHR($8A)+CHR($B1)
1706 std::string s(text);
1707
1708 if (multiline) { // filter parameter, true when lsp is given
1709 // multiline case: string contains '\' and 'n' characters explicitely
1710 // SubTitle compatibility: literal "\n" means line break, but "\\n" means that literal "\n" will be printed
1711 // Thus we replace two-character literal "\n" to \n (0x0A) then when "\" and \n found, we change it back to literal "\n"
1712 size_t index = 0;
1713 while (true) {
1714 index = s.find("\\n", index);
1715 if (index == std::string::npos) break;
1716 s.replace(index, 1, "\n");
1717 s.erase(index + 1, 1); // two characters replaced by a single one, erase at the second position
1718 index += 1; // length of the string to replace
1719 }
1720 // '\' and '\n' back to literal "\n"
1721 index = 0;
1722 while (true) {
1723 index = s.find("\\\n", index); // yes, \ and \n
1724 if (index == std::string::npos) break;
1725 s.replace(index, 2, "\\n"); // back to "\" + "n"
1726 index += 2; // length of the string to replace
1727 }
1728 }
1729
1730 std::string s_utf8 = charToUtf8(s.c_str(), utf8); // to wchar_t either from utf8 (win/linux) or ansi (win)
1731
1732 // halocolor MSB
1733 // FF: fadeIt, no halo
1734 // FE: fadeIt, use halocolor
1735 // 01-FD: no halo
1736 // 00: use halocolor
1737 int halocolor_msb = (halocolor_orig & 0xFF000000) >> 24;
1738 SimpleTextOutW_multi(current_font.get(), vi, frame, real_x, real_y, s_utf8,
1739 halocolor_msb == 0xFF || halocolor_msb == 0xFE, // fadeIt, special halocolor, when MSB byte is FF or FE
1740 textcolor, halocolor,
1741 halocolor_msb == 0x00 || halocolor_msb == 0xFE, // use halocolor when MSB byte is 00 or FE
1742 align, lsp, chromalocation);
1743 }
1744
1745 return frame;
1746 }
1747
1748 AVSValue __cdecl SimpleText::Create(AVSValue args, void*, IScriptEnvironment* env)
1749 {
1750 PClip clip = args[0].AsClip();
1751 VideoInfo vi = clip->GetVideoInfo();
1752
1753 const char* text = args[1].AsString();
1754 const int first_frame = args[4].AsInt(0);
1755 const int last_frame = args[5].AsInt(vi.num_frames - 1);
1756 const char* font = args[6].AsString("Terminus"); // Terminus, info_h are embedded
1757 const int size = int(args[7].AsFloat(18)); // height 12, 14, 16, 18, 20, 24, 28, 32
1758 const int text_color = args[8].AsInt(0xFFFF00);
1759 const int halo_color = args[9].AsInt(0);
1760 // Warning: if x=-1 passed + no align specified: sets bottom center alignment.
1761 // This is why: SubTitle definition: Can be set to -1 to automatically center the text horizontally or vertically, respectively.
1762 // Negative values of x and y not equal to -1 can be used to move subtitles partially off the screen.
1763 // Contrary to the documentation, y=-1 is not checked at all
1764 const int align = args[10].AsInt(args[2].AsFloat(0) == -1 ? 2 : 7);
1765 const int spc = args[11].AsInt(0);
1766 const bool multiline = args[12].Defined();
1767 const int lsp = args[12].AsInt(0); // line spacing if multiline
1768 const int font_width = int(args[13].AsFloat(0) * 8 + 0.5); // n/a
1769 const int font_angle = int(args[14].AsFloat(0) * 10 + 0.5); // n/a
1770 const bool interlaced = args[15].AsBool(false); // n/a
1771 const char* font_filename = args[16].AsString("");
1772 const bool utf8 = args[17].AsBool(false); // linux: n/a
1773 const bool bold = args[18].AsBool(false); // valid SubTitle parameter since v3.7.3
1774 [[maybe_unused]] const bool italic = args[19].AsBool(false); // valid SubTitle parameter since v3.7.3, but in "Text" not implemented
1775 [[maybe_unused]] const bool noaa = args[20].AsBool(false); // valid SubTitle parameter since v3.7.3, but in "Text" not implemented
1776 // "placement" at [21], "gdi" at [22] — identical for both "Text" and "Subtitle" no-GDI fallback
1777 const char* placement_name = args.ArraySize() >= 22 ? args[21].AsString(nullptr) : nullptr;
1778 [[maybe_unused]] const bool gdi = args.ArraySize() >= 23 ? args[22].AsBool(false) : false; // ignored by SimpleText
1779
1780 // parameters marked with n/a are ignored; parameter list is currently the same as SubTitle
1781
1782 if ((align < 1) || (align > 9))
1783 env->ThrowError("SimpleText: Align values are 1 - 9 mapped to your numeric pad");
1784
1785 int defx, defy;
1786 bool x_center = false;
1787 bool y_center = false;
1788
1789 switch (align) {
1790 case 1: case 4: case 7: defx = 8; break;
1791 case 2: case 5: case 8:
1792 defx = 0; // n/a if not set later
1793 x_center = true;
1794 break;
1795 case 3: case 6: case 9: defx = clip->GetVideoInfo().width - 8; break;
1796 default: defx = 8; break;
1797 }
1798 switch (align) {
1799 case 1: case 2: case 3: defy = clip->GetVideoInfo().height - 2; break;
1800 case 4: case 5: case 6:
1801 defy = 0; // n/a if not set later
1802 y_center = true;
1803 break;
1804 case 7: case 8: case 9: defy = 0; break;
1805 default: defy = /*(size + 4) / 8;*/ (size + 1) / 2; break; // no mul 8
1806 }
1807
1808 const bool isXdefined = args[2].Defined();
1809 const bool isYdefined = args[3].Defined();
1810 // prevent turning exactly given x or y == -1 into 0, use lround instead of int cast. int(-1 + 0.5) --> 0 :(
1811 const int x = std::lround(args[2].AsDblDef(defx));
1812 const int y = std::lround(args[3].AsDblDef(defy));
1813
1814 int real_x = x;
1815 int real_y = y;
1816
1817 // center check
1818 if (!isXdefined && x_center)
1819 real_x = (clip->GetVideoInfo().width >> 1) /* * 8 */; // no mul 8 like in SubTitle
1820
1821 if (!isYdefined && y_center)
1822 real_y = (clip->GetVideoInfo().height >> 1) /* * 8 */; // no mul 8 like in SubTitle
1823
1824 // anyway, we accept any chroma location here.
1825 // "Text" filter will ignore invalid/not used definitions and use its defaults
1826 int ChromaLocation_In = -1; // invalid
1827
1828 if (vi.IsYV411()) {
1829 // placement parameter exists, (default none/-1) + input frame properties; 'left'-ish _ChromaLocation is allowed, checked later
1830 auto frame0 = clip->GetFrame(0, env);
1831 const AVSMap* props = env->getFramePropsRO(frame0);
1832 chromaloc_parse_merge_with_props(vi, placement_name, props, /* ref*/ChromaLocation_In, -1 /*default none chromaloc */, env);
1833 }
1834 else if (vi.Is420() || vi.Is422() || vi.IsYUY2()) {
1835 // placement parameter is valid + input frame properties
1836 auto frame0 = clip->GetFrame(0, env);
1837 const AVSMap* props = env->getFramePropsRO(frame0);
1838 chromaloc_parse_merge_with_props(vi, placement_name, props, /* ref*/ChromaLocation_In, ChromaLocation_e::AVS_CHROMA_LEFT /*default*/, env);
1839 }
1840
1841 return new SimpleText(clip, text, real_x, real_y, first_frame, last_frame, font, size, text_color,
1842 halo_color, align, spc, multiline, lsp, font_width, font_angle, interlaced, font_filename, utf8, bold, ChromaLocation_In, env);
1843 }
1844
1845
1846
1847 /***********************************
1848 ******* FilterInfo Filter ******
1849 **********************************/
1850
1851 FilterInfo::FilterInfo( PClip _child, const char _fontname[], int _size, int _textcolor, int _halocolor, bool _bold, bool _italic, bool _noaa,
1852 bool _cpu, int _x, int _y, int _align, bool _gdi, IScriptEnvironment* env)
1853 : GenericVideoFilter(_child), vii(AdjustVi()), size(_size),
1854 text_color(vi.IsYUV() || vi.IsYUVA() ? RGB2YUV_Rec601(_textcolor) : _textcolor),
1855 halo_color(vi.IsYUV() || vi.IsYUVA() ? RGB2YUV_Rec601(_halocolor) : _halocolor),
1856 #if defined(AVS_WINDOWS) && !defined(NO_WIN_GDI)
1857 use_gdi(_gdi),
1858 antialiaser(_gdi ? std::make_unique<Antialiaser>(vi.width, vi.height, _fontname, size,
1859 vi.IsYUV() || vi.IsYUVA() ? RGB2YUV_Rec601(_textcolor) : _textcolor,
1860 vi.IsYUV() || vi.IsYUVA() ? RGB2YUV_Rec601(_halocolor) : _halocolor,
1861 _bold, _italic, _noaa,
1862 env->GetCPUFlagsEx(), ChromaLocation_e::AVS_CHROMA_CENTER, /* quick */
1863 0, 0, false) : nullptr),
1864 #else
1865 use_gdi(false),
1866 #endif
1867 bold(_bold), italic(_italic), noaa(_noaa),
1868 cpu(_cpu), x(_x), y(_y), align(_align)
1869 {
1870 AVS_UNUSED(env);
1871
1872 if (!use_gdi) {
1873 chromaplacement = ChromaLocation_e::AVS_CHROMA_LEFT;
1874 current_font = GetBitmapFont(size, "Terminus", bold, false);
1875 if (current_font == nullptr) {
1876 current_font = GetBitmapFont(size, "", bold, false);
1877 if (current_font == nullptr)
1878 current_font = GetBitmapFont(size, "", !bold, false);
1879 }
1880 }
1881 }
1882
1883
1884 FilterInfo::~FilterInfo(void)
1885 {
1886 }
1887
1888
1889 const VideoInfo& FilterInfo::AdjustVi()
1890 {
1891 if ( !vi.HasVideo() ) {
1892 vi.fps_denominator=1;
1893 vi.fps_numerator=24;
1894 vi.height=480;
1895 vi.num_frames=240;
1896 vi.pixel_type=VideoInfo::CS_BGR32;
1897 vi.width=640;
1898 vi.SetFieldBased(false);
1899 }
1900 return child->GetVideoInfo();
1901 }
1902
1903
1904 const char* const t_INT8="Integer 8 bit";
1905 const char* const t_INT16="Integer 16 bit";
1906 const char* const t_INT24="Integer 24 bit";
1907 const char* const t_INT32="Integer 32 bit";
1908 const char* const t_FLOAT32="Float 32 bit";
1909 const char* const t_YES="YES";
1910 const char* const t_NO="NO";
1911 const char* const t_NONE="NONE";
1912 const char* const t_TFF ="Top Field First ";
1913 const char* const t_BFF ="Bottom Field First ";
1914 const char* const t_ATFF="Assumed Top Field First ";
1915 const char* const t_ABFF="Assumed Bottom Field First ";
1916 const char* const t_STFF="Top Field (Separated) ";
1917 const char* const t_SBFF="Bottom Field (Separated) ";
1918
1919 static std::string GetCacheInfo(IScriptEnvironment* env)
1920 {
1921 std::stringstream ss;
1922 size_t l2_cache_size = env->GetEnvProperty(AEP_CACHESIZE_L2);
1923 if (l2_cache_size > 0)
1924 ss << "L2 Cache Size: " << l2_cache_size << " bytes";
1925 return ss.str();
1926 }
1927
1928 #ifdef INTEL_INTRINSICS
1929 std::string GetCpuMsg(IScriptEnvironment * env, bool avx512)
1930 {
1931 int64_t flags = env->GetCPUFlagsEx();
1932 std::stringstream ss;
1933
1934 if (!avx512) {
1935 #ifndef _M_X64
1936 // don't display old capabilities when at least AVX is used
1937 if (!(flags & CPUF_AVX)) {
1938 //if (flags & CPUF_FPU)
1939 // ss << "x87 ";
1940 if (flags & CPUF_MMX)
1941 ss << "MMX ";
1942 if (flags & CPUF_INTEGER_SSE)
1943 ss << "ISSE ";
1944
1945 if (flags & CPUF_3DNOW_EXT)
1946 ss << "3DNOW_EXT";
1947 else if (flags & CPUF_3DNOW)
1948 ss << "3DNOW ";
1949 }
1950
1951 if (flags & CPUF_SSE)
1952 ss << "SSE ";
1953 #endif
1954 if (flags & CPUF_SSE2)
1955 ss << "SSE2 ";
1956 if (flags & CPUF_SSE3)
1957 ss << "SSE3 ";
1958 if (flags & CPUF_SSSE3)
1959 ss << "SSSE3 ";
1960 if (flags & CPUF_SSE4_1)
1961 ss << "SSE4.1 ";
1962 if (flags & CPUF_SSE4_2)
1963 ss << "SSE4.2 ";
1964
1965 if (flags & CPUF_AVX)
1966 ss << "AVX ";
1967 if (flags & CPUF_AVX2)
1968 ss << "AVX2 ";
1969 if (flags & CPUF_FMA3)
1970 ss << "FMA3 ";
1971 if (flags & CPUF_FMA4)
1972 ss << "FMA4 ";
1973 if (flags & CPUF_F16C)
1974 ss << "F16C ";
1975 }
1976 else {
1977
1978 if (flags & CPUF_AVX512_MASK)
1979 ss << "AVX512 ";
1980 // Core AVX-512 Extensions as a distinct flag (F, CD, BW, DQ, VL)
1981 if (flags & CPUF_AVX512_BASE)
1982 ss << "BASE (";
1983
1984 // Base part (F, CD, BW, DQ, VL)
1985 if (flags & CPUF_AVX512F) ss << "F ";
1986 if (flags & CPUF_AVX512CD) ss << "CD ";
1987 if (flags & CPUF_AVX512BW) ss << "BW ";
1988 if (flags & CPUF_AVX512DQ) ss << "DQ ";
1989 if (flags & CPUF_AVX512VL) {
1990 if (flags & CPUF_AVX512_BASE)
1991 ss << "VL) ";
1992 else
1993 ss << "VL ";
1994 }
1995
1996 // ICL part as a distinct flag:
1997 if (flags & CPUF_AVX512_FAST) // Ice Lake/Rocket Lake extensions; usable avx-512
1998 ss << "FAST (";
1999 if (flags & CPUF_AVX512VNNI) ss << "VNNI ";
2000 if (flags & CPUF_AVX512VBMI) ss << "VBMI ";
2001 if (flags & CPUF_AVX512VBMI2) ss << "VBMI2 ";
2002 if (flags & CPUF_AVX512BITALG) ss << "BITALG ";
2003 if (flags & CPUF_AVX512VPOPCNTDQ) {
2004 if (flags & CPUF_AVX512_FAST)
2005 ss << "VPOPCNTDQ) ";
2006 else
2007 ss << "VPOPCNTDQ ";
2008 }
2009
2010 // rest of AVX-512 extensions:
2011 if (flags & CPUF_AVX512IFMA) ss << "IFMA ";
2012 if (flags & CPUF_AVX512BF16) ss << "BF16 ";
2013 if (flags & CPUF_AVX512FP16) ss << "FP16 ";
2014 if (flags & CPUF_AVX512PF) ss << "PF ";
2015 if (flags & CPUF_AVX512ER) ss << "ER ";
2016
2017 // Crypto (VAES, VPCLMULQDQ, GFNI) and deprecated (VP2INTERSECT, 4VNNIW, 4FMAPS) extensions excluded
2018 }
2019 return ss.str();
2020 }
2021 #elif defined(ARM64)
2022 // aarch64 ARMv8-A flags
2023 std::string GetCpuMsg(IScriptEnvironment* env)
2024 {
2025 int64_t flags = env->GetCPUFlagsEx();
2026 std::stringstream ss;
2027
2028 // Tier 1: CPUF_ARM_NEON (Baseline)
2029 if (flags & CPUF_ARM_NEON)
2030 ss << "NEON ";
2031
2032 if (flags & CPUF_ARM_DOTPROD)
2033 ss << "DOTPROD ";
2034
2035 if (flags & CPUF_ARM_I8MM)
2036 ss << "I8MM ";
2037
2038 if (flags & CPUF_ARM_SVE2)
2039 ss << "SVE2 ";
2040
2041 if (flags & CPUF_ARM_SVE2_1)
2042 ss << "SVE2.1 ";
2043
2044 return ss.str();
2045 }
2046 #else
2047 std::string GetCpuMsg(IScriptEnvironment * env)
2048 {
2049 std::stringstream ss;
2050
2051 return ss.str();
2052 }
2053 #endif
2054
2055 bool FilterInfo::GetParity(int n)
2056 {
2057 return vii.HasVideo() ? child->GetParity(n) : false;
2058 }
2059
2060
2061 PVideoFrame FilterInfo::GetFrame(int n, IScriptEnvironment* env)
2062 {
2063 PVideoFrame frame = vii.HasVideo() ? child->GetFrame(n, env) : env->NewVideoFrame(vi);
2064
2065 if ( !vii.HasVideo() ) {
2066 memset(frame->GetWritePtr(), 0, frame->GetPitch()*frame->GetHeight()); // Blank frame
2067 }
2068
2069 #if defined(AVS_WINDOWS) && !defined(NO_WIN_GDI)
2070 HDC hdcAntialias = nullptr;
2071 if (use_gdi) {
2072 hdcAntialias = antialiaser->GetDC();
2073 if (!hdcAntialias)
2074 return frame;
2075 } else if (current_font == nullptr)
2076 return frame;
2077 #else
2078 if (current_font == nullptr)
2079 return frame;
2080 #endif
2081 const char* c_space = "Unknown";
2082 const char* s_type = t_NONE;
2083 const char* s_parity;
2084 char text[1024];
2085 int tlen;
2086 std::string chn_layout_str;
2087
2088 if (vii.HasVideo()) {
2089 c_space = GetPixelTypeName(vii.pixel_type);
2090 if (*c_space == '\0')
2091 c_space = "Unknown";
2092 if (vii.IsFieldBased()) {
2093 if (child->GetParity(n)) {
2094 s_parity = t_STFF;
2095 }
2096 else {
2097 s_parity = t_SBFF;
2098 }
2099 }
2100 else {
2101 if (child->GetParity(n)) {
2102 s_parity = vii.IsTFF() ? t_ATFF : t_TFF;
2103 }
2104 else {
2105 s_parity = vii.IsBFF() ? t_ABFF : t_BFF;
2106 }
2107 }
2108 uint64_t vLenInMsecs = (uint64_t)(1000.0 * (double)vii.num_frames * (double)vii.fps_denominator / (double)vii.fps_numerator);
2109 uint64_t cPosInMsecs = (uint64_t)(1000.0 * (double)n * (double)vii.fps_denominator / (double)vii.fps_numerator);
2110
2111 tlen = snprintf(text, sizeof(text),
2112 "Frame: %8u of %-8u\n" // 28
2113 "Time: %02" PRIu64 ":%02d:%02d.%03d of %02" PRIu64 ":%02d:%02d.%03d\n" // 35
2114 "ColorSpace: %s, BitsPerComponent: %u\n" // 18=13+5
2115 // "Bits per component: %2u\n" // 22
2116 "Width:%4u pixels, Height:%4u pixels\n" // 39
2117 "Frames per second: %7.4f (%u/%u)\n" // 51=31+20
2118 "FieldBased (Separated) Video: %s\n" // 35=32+3
2119 "Parity: %s\n" // 35=9+26
2120 "Video Pitch: %5u bytes.\n" // 25
2121 "Has Audio: %s\n" // 15=12+3
2122 // "123456789012345678901234567890123456789012345678901234567890\n" // test
2123 , n, vii.num_frames
2124 , (cPosInMsecs / (60 * 60 * 1000)), (int)((cPosInMsecs / (60 * 1000)) % 60), (int)((cPosInMsecs / 1000) % 60), (int)(cPosInMsecs % 1000),
2125 (vLenInMsecs / (60 * 60 * 1000)), (int)((vLenInMsecs / (60 * 1000)) % 60), (int)((vLenInMsecs / 1000) % 60), (int)(vLenInMsecs % 1000)
2126 , c_space
2127 , vii.BitsPerComponent()
2128 , vii.width, vii.height
2129 , (float)vii.fps_numerator / (float)vii.fps_denominator, vii.fps_numerator, vii.fps_denominator
2130 , vii.IsFieldBased() ? t_YES : t_NO
2131 , s_parity
2132 , frame->GetPitch()
2133 , vii.HasAudio() ? t_YES : t_NO
2134 );
2135 }
2136 else {
2137 tlen = snprintf(text, sizeof(text),
2138 "Frame: %8u of %-8u\n"
2139 "Has Video: NO\n"
2140 "Has Audio: %s\n"
2141 , n, vi.num_frames
2142 , vii.HasAudio() ? t_YES : t_NO
2143 );
2144 }
2145 if (vii.HasAudio()) {
2146 if (vii.SampleType() == SAMPLE_INT8) s_type = t_INT8;
2147 else if (vii.SampleType() == SAMPLE_INT16) s_type = t_INT16;
2148 else if (vii.SampleType() == SAMPLE_INT24) s_type = t_INT24;
2149 else if (vii.SampleType() == SAMPLE_INT32) s_type = t_INT32;
2150 else if (vii.SampleType() == SAMPLE_FLOAT) s_type = t_FLOAT32;
2151
2152 uint64_t aLenInMsecs = static_cast<uint64_t>(1000.0 * (double)vii.num_audio_samples / (double)vii.audio_samples_per_second);
2153 tlen += snprintf(text + tlen, sizeof(text) - tlen,
2154 "Audio Channels: %-8u\n" // 25
2155 "Sample Type: %s\n" // 28=14+14
2156 "Samples Per Second: %5d\n" // 26
2157 "Audio length: %" PRIu64 " samples. %02" PRIu64 ":%02d:%02d.%03d\n" // 57=37+20
2158 , vii.AudioChannels()
2159 , s_type
2160 , vii.audio_samples_per_second
2161 , vii.num_audio_samples,
2162 (aLenInMsecs / (60 * 60 * 1000)), (int)((aLenInMsecs / (60 * 1000)) % 60), (int)((aLenInMsecs / 1000) % 60), (int)(aLenInMsecs % 1000)
2163 );
2164 if (vi.IsChannelMaskKnown()) {
2165 chn_layout_str = channel_layout_to_str(vi.GetChannelMask());
2166 tlen += snprintf(text + tlen, sizeof(text) - tlen,
2167 "Channel mask: %s\n", chn_layout_str.c_str());
2168 }
2169 }
2170 else {
2171 if (cpu) {
2172 strcpy(text + tlen, "\n");
2173 tlen += 1;
2174 }
2175 }
2176 if (cpu) {
2177 // CPU capabilities
2178 tlen += snprintf(text + tlen, sizeof(text) - tlen,
2179 "CPU: %s\n"
2180 #ifdef INTEL_INTRINSICS
2181 , GetCpuMsg(env, false).c_str()
2182 #else
2183 , GetCpuMsg(env).c_str()
2184 #endif
2185 );
2186 #ifdef INTEL_INTRINSICS
2187 // AVX512 flags in new line (too long)
2188 std::string avx512 = GetCpuMsg(env, true);
2189 if (avx512.length() > 0) {
2190 tlen += snprintf(text + tlen, sizeof(text) - tlen,
2191 "%s\n"
2192 , avx512.c_str()
2193 );
2194 }
2195 #endif
2196 // Cache size info in new line
2197 std::string cache_info = GetCacheInfo(env);
2198 if (cache_info.length() > 0) {
2199 tlen += snprintf(text + tlen, sizeof(text) - tlen,
2200 "%s\n",
2201 cache_info.c_str()
2202 );
2203 }
2204 } // show cpu capabilities
2205
2206 // Windows GDI: aligns the whole box, its content is kept top left aligned
2207 // "Text" fixed font mode: individual lines are aligned as well
2208
2209 #if defined(AVS_WINDOWS) && !defined(NO_WIN_GDI)
2210 if (use_gdi) {
2211 // So far RECT dimensions were hardcoded: RECT r = { 32, 16, min(3440,vi.width * 8), 900*2 };
2212 // More flexible way: get text extent
2213 RECT r;
2214
2215 // DrawText calculates the extent as plus one line if text ends with an empty \n
2216 // thus calculation of vertical center and bottom aligned text would be wrong.
2217 // Cut ending LFs
2218 while (tlen > 1 && text[tlen - 1] == '\n') {
2219 text[tlen - 1] = 0;
2220 tlen--;
2221 }
2222
2223 RECT r0 = { 0, 0, 100, 100 };
2224
2225 // always draw box at top left aligned, we calculate coordinates instead
2226 // depending on the given alignment
2227 SetTextAlign(hdcAntialias, TA_TOP | TA_LEFT);
2228 // calculate box extent without drawing
2229 DrawText(hdcAntialias, text, -1, &r0, DT_CALCRECT | DT_NOCLIP);
2230
2231 // correct top left and right bottom of the whole info box
2232 int real_x = x;
2233 int real_y = y;
2234 // align vertical center
2235 if (align == 4 || align == 5 || align == 6) {
2236 real_y -= (int)((r0.bottom- r0.top) / 2.0 + 0.5);
2237 }
2238 // align vertical bottom
2239 if (align == 1 || align == 2 || align == 3) {
2240 real_y -= (r0.bottom - r0.top);
2241 }
2242 // align horizontal center
2243 if (align == 8 || align == 5 || align == 2) {
2244 real_x -= (int)((r0.right - r0.left) / 2.0 + 0.5);
2245 }
2246 // align horizontal right
2247 if (align == 9 || align == 6 || align == 3) {
2248 real_x -= (r0.right - r0.left);
2249 }
2250
2251 int right = real_x + (int)r0.right;
2252 int bottom = real_y + (int)r0.bottom;
2253 // not left aligned: crop to visible right
2254 if (!(align == 9 || align == 6 || align == 3))
2255 right = min(right, vi.width * 8 - 1);
2256 // not bottom aligned: crop to visible bottom
2257 if (!(align == 1 || align == 2 || align == 3))
2258 bottom = min(bottom, vi.height * 8 - 1);
2259
2260 r = { real_x, real_y, right, bottom };
2261
2262 // NOCLIP: draw lines if they are partially visible
2263 DrawText(hdcAntialias, text, -1, &r, DT_NOCLIP);
2264 GdiFlush();
2265
2266 env->MakeWritable(&frame);
2267 frame->GetWritePtr(); // Bump sequence_number
2268 int dst_pitch = frame->GetPitch();
2269 antialiaser->Apply(vi, &frame, dst_pitch);
2270 } else {
2271 env->MakeWritable(&frame);
2272 frame->GetWritePtr(); // Bump sequence_number
2273
2274 bool utf8 = false;
2275 std::string s_utf8 = charToUtf8(text, utf8);
2276 int lsp = 0;
2277 SimpleTextOutW_multi(current_font.get(), vi, frame, x, y, s_utf8, false, text_color, halo_color, true, align, lsp, chromaplacement);
2278 }
2279 #else
2280 env->MakeWritable(&frame);
2281 frame->GetWritePtr(); // Bump sequence_number
2282
2283 // AVS_POSIX: utf8 is always true, here n/a
2284 bool utf8 = false;
2285 std::string s_utf8 = charToUtf8(text, utf8);
2286
2287 int lsp = 0; // line spacing n/a
2288 SimpleTextOutW_multi(current_font.get(), vi, frame, x, y, s_utf8, false, text_color, halo_color, true, align, lsp, chromaplacement);
2289 #endif
2290 return frame;
2291 }
2292
2293 AVSValue __cdecl FilterInfo::Create(AVSValue args, void*, IScriptEnvironment* env)
2294 {
2295 // 0 1 2 3 4 5 6 7 8 9 10 11 12
2296 // c[font]s[size]f[text_color]i[halo_color]i[bold]b[italic]b[noaa]b[cpu]b[x]f[y]f[align]i[gdi]b
2297 PClip clip = args[0].AsClip();
2298 #if defined(AVS_WINDOWS) && !defined(NO_WIN_GDI)
2299 const bool gdi = args[12].AsBool(true);
2300 #else
2301 const bool gdi = false;
2302 #endif
2303 const char* font = args[1].AsString(gdi ? "Courier New" : "Terminus");
2304 const int upper_limit = gdi ? -1 : 32;
2305 int size = gdi ? int(args[2].AsFloat(0) * 8 + 0.5) : int(args[2].AsFloat(0));
2306 if (!args[2].Defined() || size < 0)
2307 size = CalcFontSizeForInfo(clip->GetVideoInfo().width, clip->GetVideoInfo().height, size < 0, upper_limit, gdi);
2308 const int text_color = args[3].AsInt(0xFFFF00);
2309 const int halo_color = args[4].AsInt(0);
2310 const bool bold = args[5].AsBool(gdi);
2311 const bool italic = args[6].AsBool(false);
2312 const bool noaa = args[7].AsBool(false);
2313 const bool cpu = args[8].AsBool(true);
2314
2315 const int align = args[11].AsInt(7); // default top left
2316
2317 const int info_default_x = 4; // subtitle: 8
2318 const int PIXEL_MUL_FACTOR = gdi ? 8 : 1;
2319 // similar to SubTitle
2320 int defx, defy;
2321 bool x_center = false;
2322 bool y_center = false;
2323
2324 switch (align) {
2325 case 1: case 4: case 7: defx = 8; break;
2326 case 2: case 5: case 8:
2327 defx = 0; // n/a if not set later
2328 x_center = true;
2329 break;
2330 case 3: case 6: case 9: defx = clip->GetVideoInfo().width - info_default_x; break; // subtitle: 8
2331 default: defx = info_default_x; break;
2332 }
2333
2334 switch (align) {
2335 case 1: case 2: case 3: defy = clip->GetVideoInfo().height - 2; break; // bottom alignment 2 pixel above
2336 case 4: case 5: case 6:
2337 defy = 0; // n/a if not set later
2338 y_center = true;
2339 break;
2340 case 7: case 8: case 9: defy = 0; break;
2341 default: defy = (size + 4) / 8; break;
2342 }
2343
2344 const bool isXdefined = args[9].Defined();
2345 const bool isYdefined = args[10].Defined();
2346
2347 int x = int(args[9].AsDblDef(defx) * PIXEL_MUL_FACTOR + 0.5);
2348 int y = int(args[10].AsDblDef(defy) * PIXEL_MUL_FACTOR + 0.5);
2349
2350 if (!isXdefined && x_center)
2351 x = (clip->GetVideoInfo().width >> 1) * PIXEL_MUL_FACTOR;
2352
2353 if (!isYdefined && y_center)
2354 y = (clip->GetVideoInfo().height >> 1) * PIXEL_MUL_FACTOR;
2355
2356
2357 if ((align < 1) || (align > 9))
2358 env->ThrowError("Info: Align values are 1 - 9 mapped to your numeric pad");
2359
2360 return new FilterInfo(clip, font, size, text_color, halo_color, bold, italic, noaa, cpu, x, y, align, gdi, env);
2361 }
2362
2363
2364
2365 /************************************
2366 ******* Compare Filter *******
2367 ***********************************/
2368
2369 6 Compare::Compare(PClip _child1, PClip _child2, const char* channels, const char *fname, bool _show_graph, bool _gdi, IScriptEnvironment* env)
2370 : GenericVideoFilter(_child1),
2371 #if defined(AVS_WINDOWS) && !defined(NO_WIN_GDI)
2372 use_gdi(_gdi),
2373 antialiaser(_gdi ? std::make_unique<Antialiaser>(vi.width, vi.height, "Courier New", 16 * 8,
2374 (vi.IsYUV() || vi.IsYUVA()) ? 0xD21092 : 0xFFFF00,
2375 (vi.IsYUV() || vi.IsYUVA()) ? 0x108080 : 0,
2376 true, false, false,
2377 env->GetCPUFlagsEx(), ChromaLocation_e::AVS_CHROMA_CENTER, /* quick */
2378 0, 0, false) : nullptr),
2379 #else
2380 6 use_gdi(false),
2381 #endif
2382
1/2
✓ Branch 6 → 7 taken 6 times.
✗ Branch 6 → 177 not taken.
6 child2(_child2),
2383 6 log(nullptr),
2384 6 show_graph(_show_graph),
2385 6 framecount(0),
2386
5/8
✓ Branch 7 → 8 taken 6 times.
✗ Branch 7 → 175 not taken.
✓ Branch 8 → 9 taken 5 times.
✓ Branch 8 → 11 taken 1 time.
✓ Branch 9 → 10 taken 5 times.
✗ Branch 9 → 175 not taken.
✗ Branch 10 → 11 not taken.
✓ Branch 10 → 12 taken 5 times.
6 text_color((vi.IsYUV() || vi.IsYUVA()) ? 0xD21092 : 0xFFFF00),
2387
7/12
✓ Branch 2 → 3 taken 6 times.
✗ Branch 2 → 171 not taken.
✓ Branch 3 → 4 taken 6 times.
✗ Branch 3 → 169 not taken.
✓ Branch 13 → 14 taken 6 times.
✗ Branch 13 → 175 not taken.
✓ Branch 14 → 15 taken 5 times.
✓ Branch 14 → 17 taken 1 time.
✓ Branch 15 → 16 taken 5 times.
✗ Branch 15 → 175 not taken.
✗ Branch 16 → 17 not taken.
✓ Branch 16 → 18 taken 5 times.
12 halo_color((vi.IsYUV() || vi.IsYUVA()) ? 0x108080 : 0)
2388 {
2389
1/2
✓ Branch 20 → 21 taken 6 times.
✗ Branch 20 → 175 not taken.
6 const VideoInfo& vi2 = child2->GetVideoInfo();
2390 6 psnrs = 0;
2391
2392
2/4
✓ Branch 21 → 22 taken 6 times.
✗ Branch 21 → 175 not taken.
✗ Branch 22 → 23 not taken.
✓ Branch 22 → 24 taken 6 times.
6 if (!vi.IsSameColorspace(vi2))
2393 env->ThrowError("Compare: Clips are not same colorspace.");
2394
2395
2/4
✓ Branch 24 → 25 taken 6 times.
✗ Branch 24 → 26 not taken.
✗ Branch 25 → 26 not taken.
✓ Branch 25 → 27 taken 6 times.
6 if (vi.width != vi2.width || vi.height != vi2.height)
2396 env->ThrowError("Compare: Clips must have same size.");
2397
2398
13/26
✓ Branch 27 → 28 taken 6 times.
✗ Branch 27 → 175 not taken.
✓ Branch 28 → 29 taken 6 times.
✗ Branch 28 → 40 not taken.
✓ Branch 29 → 30 taken 6 times.
✗ Branch 29 → 175 not taken.
✓ Branch 30 → 31 taken 6 times.
✗ Branch 30 → 40 not taken.
✓ Branch 31 → 32 taken 6 times.
✗ Branch 31 → 175 not taken.
✓ Branch 32 → 33 taken 4 times.
✓ Branch 32 → 40 taken 2 times.
✓ Branch 33 → 34 taken 4 times.
✗ Branch 33 → 175 not taken.
✓ Branch 34 → 35 taken 3 times.
✓ Branch 34 → 40 taken 1 time.
✓ Branch 35 → 36 taken 3 times.
✗ Branch 35 → 175 not taken.
✗ Branch 36 → 37 not taken.
✓ Branch 36 → 40 taken 3 times.
✗ Branch 37 → 38 not taken.
✗ Branch 37 → 175 not taken.
✗ Branch 38 → 39 not taken.
✗ Branch 38 → 40 not taken.
✗ Branch 41 → 42 not taken.
✓ Branch 41 → 43 taken 6 times.
6 if (!(vi.IsRGB24() || vi.IsYUY2() || vi.IsRGB32() || vi.IsPlanar() || vi.IsRGB48() || vi.IsRGB64()))
2399 env->ThrowError("Compare: Clips have unknown pixel format. RGB24/32/48/64, YUY2 and YUV/RGB Planar supported.");
2400
2401
1/2
✓ Branch 43 → 44 taken 6 times.
✗ Branch 43 → 175 not taken.
6 pixelsize = vi.ComponentSize();
2402
1/2
✓ Branch 44 → 45 taken 6 times.
✗ Branch 44 → 175 not taken.
6 bits_per_pixel = vi.BitsPerComponent();
2403
2404
1/2
✗ Branch 45 → 46 not taken.
✓ Branch 45 → 47 taken 6 times.
6 if (pixelsize == 4)
2405 env->ThrowError("Compare: Float pixel format not supported.");
2406
2407
1/2
✗ Branch 47 → 48 not taken.
✓ Branch 47 → 63 taken 6 times.
6 if (channels[0] == 0) {
2408 if (vi.IsRGB())
2409 channels = "RGB";
2410 else if (vi.IsY())
2411 channels = "Y";
2412 else if (vi.IsYUV() || vi.IsYUVA())
2413 channels = "YUV";
2414 else env->ThrowError("Compare: Clips have unknown colorspace. RGB and YUV supported.");
2415 }
2416
2417 6 planar_plane = 0;
2418 6 mask = 0;
2419 6 const size_t length = strlen(channels);
2420
2/2
✓ Branch 132 → 64 taken 14 times.
✓ Branch 132 → 133 taken 6 times.
20 for (size_t i = 0; i < length; i++) {
2421
7/10
✓ Branch 64 → 65 taken 14 times.
✗ Branch 64 → 175 not taken.
✓ Branch 65 → 66 taken 13 times.
✓ Branch 65 → 69 taken 1 time.
✓ Branch 66 → 67 taken 13 times.
✗ Branch 66 → 175 not taken.
✓ Branch 67 → 68 taken 13 times.
✗ Branch 67 → 69 not taken.
✓ Branch 70 → 71 taken 13 times.
✓ Branch 70 → 92 taken 1 time.
14 if (vi.IsRGB() && !vi.IsPlanar()) {
2422
3/5
✓ Branch 71 → 72 taken 4 times.
✓ Branch 71 → 73 taken 4 times.
✓ Branch 71 → 74 taken 5 times.
✗ Branch 71 → 75 not taken.
✗ Branch 71 → 83 not taken.
13 switch (channels[i]) {
2423 4 case 'b':
2424 4 case 'B': mask |= 0x000000ff; mask64 |= 0x000000000000ffffull; break;
2425 4 case 'g':
2426 4 case 'G': mask |= 0x0000ff00; mask64 |= 0x00000000ffff0000ull; break;
2427 5 case 'r':
2428 5 case 'R': mask |= 0x00ff0000; mask64 |= 0x0000ffff00000000ull; break;
2429 case 'a':
2430 case 'A': mask |= 0xff000000; mask64 |= 0xffff000000000000ull; if (vi.IsRGB32() || vi.IsRGB64()) break; // else no alpha -> fall thru
2431 default: env->ThrowError("Compare: invalid channel: %c", channels[i]);
2432 }
2433
7/10
✓ Branch 84 → 85 taken 13 times.
✗ Branch 84 → 175 not taken.
✓ Branch 85 → 86 taken 13 times.
✗ Branch 85 → 88 not taken.
✓ Branch 86 → 87 taken 13 times.
✗ Branch 86 → 175 not taken.
✓ Branch 87 → 88 taken 7 times.
✓ Branch 87 → 89 taken 6 times.
✓ Branch 90 → 91 taken 7 times.
✓ Branch 90 → 131 taken 6 times.
13 if (vi.IsRGB24() || vi.IsRGB48()) mask &= 0x00ffffff; // no alpha channel in RGB24
2434
2/4
✓ Branch 92 → 93 taken 1 time.
✗ Branch 92 → 175 not taken.
✓ Branch 93 → 94 taken 1 time.
✗ Branch 93 → 126 not taken.
1 } else if (vi.IsPlanar()) {
2435
3/10
✓ Branch 94 → 95 taken 1 time.
✗ Branch 94 → 175 not taken.
✗ Branch 95 → 96 not taken.
✓ Branch 95 → 98 taken 1 time.
✗ Branch 96 → 97 not taken.
✗ Branch 96 → 175 not taken.
✗ Branch 97 → 98 not taken.
✗ Branch 97 → 99 not taken.
✓ Branch 100 → 101 taken 1 time.
✗ Branch 100 → 117 not taken.
1 if(vi.IsYUV() || vi.IsYUVA()) {
2436
1/5
✓ Branch 101 → 102 taken 1 time.
✗ Branch 101 → 103 not taken.
✗ Branch 101 → 104 not taken.
✗ Branch 101 → 105 not taken.
✗ Branch 101 → 108 not taken.
1 switch (channels[i]) {
2437 1 case 'y':
2438 1 case 'Y': mask |= 0xffffffff; planar_plane |= PLANAR_Y; break;
2439 case 'u':
2440 case 'U': mask |= 0xffffffff; planar_plane |= PLANAR_U; break;
2441 case 'v':
2442 case 'V': mask |= 0xffffffff; planar_plane |= PLANAR_V; break;
2443 case 'a':
2444 case 'A': mask |= 0xffffffff; planar_plane |= PLANAR_A; if (vi.IsYUVA()) break; // else no alpha -> fall thru
2445 default: env->ThrowError("Compare: invalid channel: %c", channels[i]);
2446 }
2447
5/10
✓ Branch 109 → 110 taken 1 time.
✗ Branch 109 → 175 not taken.
✓ Branch 110 → 111 taken 1 time.
✗ Branch 110 → 114 not taken.
✓ Branch 111 → 112 taken 1 time.
✗ Branch 111 → 113 not taken.
✗ Branch 112 → 113 not taken.
✓ Branch 112 → 114 taken 1 time.
✗ Branch 115 → 116 not taken.
✓ Branch 115 → 125 taken 1 time.
1 if (vi.IsY() && ((planar_plane & PLANAR_U) || (planar_plane & PLANAR_V))) {
2448 env->ThrowError("Compare: invalid channel: %c for greyscale clip", channels[i]);
2449 }
2450 } else {
2451 // planar RGB, planar RGBA
2452 switch (channels[i]) {
2453 case 'r':
2454 case 'R': mask |= 0xffffffff; planar_plane |= PLANAR_R; break;
2455 case 'g':
2456 case 'G': mask |= 0xffffffff; planar_plane |= PLANAR_G; break;
2457 case 'b':
2458 case 'B': mask |= 0xffffffff; planar_plane |= PLANAR_B; break;
2459 case 'a':
2460 case 'A': mask |= 0xffffffff; planar_plane |= PLANAR_A; if (vi.IsPlanarRGBA()) break; // else no alpha -> fall thru
2461 default: env->ThrowError("Compare: invalid channel: %c", channels[i]);
2462 }
2463 }
2464 } else { // YUY2
2465 switch (channels[i]) {
2466 case 'y':
2467 case 'Y': mask |= 0x00ff00ff; break;
2468 case 'u':
2469 case 'U': mask |= 0x0000ff00; break;
2470 case 'v':
2471 case 'V': mask |= 0xff000000; break;
2472 default: env->ThrowError("Compare: invalid channel: %c", channels[i]);
2473 }
2474 }
2475 }
2476
2477 6 masked_bytes = 0;
2478
2/2
✓ Branch 135 → 134 taken 19 times.
✓ Branch 135 → 136 taken 6 times.
25 for (uint32_t temp = mask; temp != 0; temp >>=8)
2479 19 masked_bytes += (temp & 1);
2480
2481
2/2
✓ Branch 136 → 137 taken 5 times.
✓ Branch 136 → 145 taken 1 time.
6 if (fname[0] != 0) {
2482
1/2
✓ Branch 137 → 138 taken 5 times.
✗ Branch 137 → 175 not taken.
5 log = fopen(fname, "wt");
2483
1/2
✓ Branch 138 → 139 taken 5 times.
✗ Branch 138 → 144 not taken.
5 if (log) {
2484
1/2
✓ Branch 139 → 140 taken 5 times.
✗ Branch 139 → 175 not taken.
5 fprintf(log,"Comparing channel(s) %s\n\n",channels);
2485
1/2
✓ Branch 140 → 141 taken 5 times.
✗ Branch 140 → 175 not taken.
5 fprintf(log," Mean Max Max \n");
2486
1/2
✓ Branch 141 → 142 taken 5 times.
✗ Branch 141 → 175 not taken.
5 fprintf(log," Absolute Mean Pos. Neg. \n");
2487
1/2
✓ Branch 142 → 143 taken 5 times.
✗ Branch 142 → 175 not taken.
5 fprintf(log," Frame Dev. Dev. Dev. Dev. PSNR (dB) \n");
2488
1/2
✓ Branch 143 → 153 taken 5 times.
✗ Branch 143 → 175 not taken.
5 fprintf(log,"-----------------------------------------------------\n");
2489 } else
2490 env->ThrowError("Compare: unable to create file %s", fname);
2491 } else {
2492
1/2
✓ Branch 145 → 146 taken 1 time.
✗ Branch 145 → 147 not taken.
1 psnrs = new(std::nothrow) int[vi.num_frames];
2493
1/2
✓ Branch 149 → 150 taken 1 time.
✗ Branch 149 → 153 not taken.
1 if (psnrs)
2494
2/2
✓ Branch 152 → 151 taken 2 times.
✓ Branch 152 → 153 taken 1 time.
3 for (int i = 0; i < vi.num_frames; i++)
2495 2 psnrs[i] = 0;
2496 }
2497
1/2
✓ Branch 153 → 154 taken 6 times.
✗ Branch 153 → 168 not taken.
6 if (!use_gdi) {
2498 6 const int size = 16;
2499
1/2
✓ Branch 154 → 155 taken 6 times.
✗ Branch 154 → 172 not taken.
6 current_font = GetBitmapFont(size, "Terminus", false, false);
2500 6 chromaplacement = ChromaLocation_e::AVS_CHROMA_LEFT;
2501
1/2
✗ Branch 158 → 159 not taken.
✓ Branch 158 → 168 taken 6 times.
6 if (current_font == nullptr) {
2502 current_font = GetBitmapFont(size, "", false, false);
2503 if (current_font == nullptr)
2504 current_font = GetBitmapFont(size, "", true, false);
2505 }
2506 }
2507 6 }
2508
2509
2510 6 Compare::~Compare()
2511 {
2512
2/2
✓ Branch 2 → 3 taken 5 times.
✓ Branch 2 → 10 taken 1 time.
6 if (log) {
2513 5 fprintf(log,"\n\n\nTotal frames processed: %d\n\n", framecount);
2514 5 fprintf(log," Minimum Average Maximum\n");
2515 5 fprintf(log,"Mean Absolute Deviation: %9.4f %9.4f %9.4f\n", MAD_min, MAD_tot/framecount, MAD_max);
2516 5 fprintf(log," Mean Deviation: %+9.4f %+9.4f %+9.4f\n", MD_min, MD_tot/framecount, MD_max);
2517 5 fprintf(log," PSNR: %9.4f %9.4f %9.4f\n", PSNR_min, PSNR_tot/framecount, PSNR_max);
2518 5 double factor = (1 << bits_per_pixel) - 1;
2519 5 double PSNR_overall = 10.0 * log10(bytecount_overall * factor * factor / SSD_overall);
2520 5 fprintf(log," Overall PSNR: %9.4f\n", PSNR_overall);
2521 5 fclose(log);
2522 }
2523
2/2
✓ Branch 10 → 11 taken 1 time.
✓ Branch 10 → 12 taken 5 times.
6 delete[] psnrs;
2524 6 }
2525
2526
2527 AVSValue __cdecl Compare::Create(AVSValue args, void*, IScriptEnvironment *env)
2528 {
2529 #if defined(AVS_WINDOWS) && !defined(NO_WIN_GDI)
2530 const bool gdi = args[5].AsBool(true);
2531 #else
2532 const bool gdi = false;
2533 #endif
2534 return new Compare( args[0].AsClip(), // clip
2535 args[1].AsClip(), // base clip
2536 args[2].AsString(""), // channels
2537 args[3].AsString(""), // logfile
2538 args[4].AsBool(true), // show_graph
2539 gdi,
2540 env);
2541 }
2542
2543 static void compare_planar_c(
2544 const BYTE * f1ptr, int pitch1,
2545 const BYTE * f2ptr, int pitch2,
2546 int rowsize, int height,
2547 int &SAD_sum, int &SD_sum, int &pos_D, int &neg_D, double &SSD_sum)
2548 {
2549 int row_SSD;
2550
2551 for (int y = 0; y < height; y++) {
2552 row_SSD = 0;
2553 for (int x = 0; x < rowsize; x += 1) {
2554 int p1 = *(f1ptr + x);
2555 int p2 = *(f2ptr + x);
2556 int d0 = p1 - p2;
2557 SD_sum += d0;
2558 SAD_sum += abs(d0);
2559 row_SSD += d0 * d0;
2560 pos_D = max(pos_D, d0);
2561 neg_D = min(neg_D, d0);
2562 }
2563 SSD_sum += row_SSD;
2564 f1ptr += pitch1;
2565 f2ptr += pitch2;
2566 }
2567 }
2568
2569 static void compare_planar_uint16_t_c(
2570 const BYTE * f1ptr8, int pitch1,
2571 const BYTE * f2ptr8, int pitch2,
2572 int rowsize, int height,
2573 int64_t&SAD_sum, int64_t &SD_sum, int &pos_D, int &neg_D, double &SSD_sum)
2574 {
2575 int64_t row_SSD;
2576
2577 const uint16_t *f1ptr = reinterpret_cast<const uint16_t *>(f1ptr8);
2578 const uint16_t *f2ptr = reinterpret_cast<const uint16_t *>(f2ptr8);
2579 pitch1 /= sizeof(uint16_t);
2580 pitch2 /= sizeof(uint16_t);
2581 rowsize /= sizeof(uint16_t);
2582
2583
2584 for (int y = 0; y < height; y++) {
2585 row_SSD = 0;
2586 for (int x = 0; x < rowsize; x += 1) {
2587 int p1 = *(f1ptr + x);
2588 int p2 = *(f2ptr + x);
2589 int d0 = p1 - p2;
2590 SD_sum += d0;
2591 SAD_sum += abs(d0);
2592 row_SSD += d0 * d0;
2593 pos_D = max(pos_D, d0);
2594 neg_D = min(neg_D, d0);
2595 }
2596 SSD_sum += row_SSD;
2597 f1ptr += pitch1;
2598 f2ptr += pitch2;
2599 }
2600 }
2601
2602
2603 static void compare_c(uint32_t mask, int increment,
2604 const BYTE * f1ptr, int pitch1,
2605 const BYTE * f2ptr, int pitch2,
2606 int rowsize, int height,
2607 int &SAD_sum, int &SD_sum, int &pos_D, int &neg_D, double &SSD_sum)
2608 {
2609 int row_SSD;
2610
2611 for (int y = 0; y < height; y++) {
2612 row_SSD = 0;
2613 for (int x = 0; x < rowsize; x += increment) {
2614 uint32_t p1 = *(uint32_t*)(f1ptr + x) & mask;
2615 uint32_t p2 = *(uint32_t*)(f2ptr + x) & mask;
2616 int d0 = (p1 & 0xff) - (p2 & 0xff);
2617 int d1 = ((p1 >> 8) & 0xff) - ((p2 & 0xff00) >> 8); // ?PF why not (p2 >> 8) & 0xff as for p1?
2618 int d2 = ((p1 >> 16) & 0xff) - ((p2 & 0xff0000) >> 16);
2619 int d3 = (p1 >> 24) - (p2 >> 24);
2620 SD_sum += d0 + d1 + d2 + d3;
2621 SAD_sum += abs(d0) + abs(d1) + abs(d2) + abs(d3);
2622 row_SSD += d0 * d0 + d1 * d1 + d2 * d2 + d3 * d3;
2623 pos_D = max(max(max(max(pos_D, d0), d1), d2), d3);
2624 neg_D = min(min(min(min(neg_D, d0), d1), d2), d3);
2625 }
2626 SSD_sum += row_SSD;
2627 f1ptr += pitch1;
2628 f2ptr += pitch2;
2629 }
2630 }
2631
2632 3 static void compare_uint16_t_c(uint64_t mask64, int increment,
2633 const BYTE * f1ptr8, int pitch1,
2634 const BYTE * f2ptr8, int pitch2,
2635 int rowsize, int height,
2636 int64_t& SAD_sum, int64_t &SD_sum, int &pos_D, int &neg_D, double &SSD_sum)
2637 {
2638 int64_t row_SSD;
2639
2640 3 const uint16_t *f1ptr = reinterpret_cast<const uint16_t *>(f1ptr8);
2641 3 const uint16_t *f2ptr = reinterpret_cast<const uint16_t *>(f2ptr8);
2642 3 pitch1 /= sizeof(uint16_t);
2643 3 pitch2 /= sizeof(uint16_t);
2644 3 rowsize /= sizeof(uint16_t);
2645
2646
2/2
✓ Branch 15 → 3 taken 3 times.
✓ Branch 15 → 16 taken 3 times.
6 for (int y = 0; y < height; y++) {
2647 3 row_SSD = 0;
2648
2/2
✓ Branch 13 → 4 taken 12 times.
✓ Branch 13 → 14 taken 3 times.
15 for (int x = 0; x < rowsize; x += increment) {
2649 12 uint64_t p1 = *(uint64_t *)(f1ptr + x) & mask64;
2650 12 uint64_t p2 = *(uint64_t *)(f2ptr + x) & mask64;
2651 12 int d0 = (p1 & 0xffff) - (p2 & 0xffff);
2652 12 int d1 = ((p1 >> 16) & 0xffff) - ((p2 & 0xffff0000) >> 16); // ?PF why not (p2 >> 16) & 0xffff as for p1?
2653 12 int d2 = ((p1 >> 32) & 0xffff) - ((p2 & 0xffff00000000ull) >> 32);
2654 12 int d3 = (p1 >> 48) - (p2 >> 48);
2655 12 SD_sum += d0 + d1 + d2 + d3;
2656 12 SAD_sum += abs(d0) + abs(d1) + abs(d2) + abs(d3);
2657 12 row_SSD += d0 * d0 + d1 * d1 + d2 * d2 + d3 * d3;
2658 12 pos_D = max(max(max(max(pos_D, d0), d1), d2), d3);
2659 12 neg_D = min(min(min(min(neg_D, d0), d1), d2), d3);
2660 }
2661 3 SSD_sum += row_SSD;
2662 3 f1ptr += pitch1;
2663 3 f2ptr += pitch2;
2664 }
2665 3 }
2666
2667
2668
2669 4 PVideoFrame __stdcall Compare::GetFrame(int n, IScriptEnvironment* env)
2670 {
2671
1/2
✓ Branch 3 → 4 taken 4 times.
✗ Branch 3 → 217 not taken.
4 PVideoFrame f1 = child->GetFrame(n, env);
2672
1/2
✓ Branch 5 → 6 taken 4 times.
✗ Branch 5 → 215 not taken.
4 PVideoFrame f2 = child2->GetFrame(n, env);
2673
2674 4 int SD = 0;
2675 4 int64_t SD_64 = 0;
2676 4 int SAD = 0;
2677 4 int64_t SAD_64 = 0;
2678 4 int pos_D = 0;
2679 4 int neg_D = 0;
2680 4 double SSD = 0;
2681
2682 4 int bytecount = 0;
2683
2684
5/8
✓ Branch 6 → 7 taken 4 times.
✗ Branch 6 → 213 not taken.
✓ Branch 7 → 8 taken 4 times.
✗ Branch 7 → 10 not taken.
✓ Branch 8 → 9 taken 4 times.
✗ Branch 8 → 213 not taken.
✓ Branch 9 → 10 taken 3 times.
✓ Branch 9 → 11 taken 1 time.
4 const int incr = (vi.IsRGB24() || vi.IsRGB48()) ? 3 : 4;
2685
2686
10/22
✓ Branch 12 → 13 taken 4 times.
✗ Branch 12 → 213 not taken.
✓ Branch 13 → 14 taken 4 times.
✗ Branch 13 → 22 not taken.
✓ Branch 14 → 15 taken 4 times.
✗ Branch 14 → 213 not taken.
✓ Branch 15 → 16 taken 4 times.
✗ Branch 15 → 22 not taken.
✓ Branch 16 → 17 taken 4 times.
✗ Branch 16 → 213 not taken.
✓ Branch 17 → 18 taken 3 times.
✓ Branch 17 → 22 taken 1 time.
✓ Branch 18 → 19 taken 3 times.
✗ Branch 18 → 213 not taken.
✗ Branch 19 → 20 not taken.
✓ Branch 19 → 22 taken 3 times.
✗ Branch 20 → 21 not taken.
✗ Branch 20 → 213 not taken.
✗ Branch 21 → 22 not taken.
✗ Branch 21 → 23 not taken.
✓ Branch 24 → 25 taken 4 times.
✗ Branch 24 → 56 not taken.
4 if (vi.IsRGB24() || vi.IsYUY2() || vi.IsRGB32() || vi.IsRGB48() || vi.IsRGB64()) {
2687
2688
1/2
✓ Branch 26 → 27 taken 4 times.
✗ Branch 26 → 213 not taken.
4 const BYTE* f1ptr = f1->GetReadPtr();
2689
1/2
✓ Branch 28 → 29 taken 4 times.
✗ Branch 28 → 213 not taken.
4 const BYTE* f2ptr = f2->GetReadPtr();
2690
1/2
✓ Branch 30 → 31 taken 4 times.
✗ Branch 30 → 213 not taken.
4 const int pitch1 = f1->GetPitch();
2691
1/2
✓ Branch 32 → 33 taken 4 times.
✗ Branch 32 → 213 not taken.
4 const int pitch2 = f2->GetPitch();
2692
1/2
✓ Branch 34 → 35 taken 4 times.
✗ Branch 34 → 213 not taken.
4 const int rowsize = f1->GetRowSize();
2693
1/2
✓ Branch 36 → 37 taken 4 times.
✗ Branch 36 → 213 not taken.
4 const int height = f1->GetHeight();
2694
2695 4 bytecount = (rowsize / pixelsize) * height * masked_bytes / 4;
2696 #ifdef INTEL_INTRINSICS
2697
2698
6/16
✓ Branch 37 → 38 taken 4 times.
✗ Branch 37 → 213 not taken.
✗ Branch 39 → 40 not taken.
✓ Branch 39 → 46 taken 1 time.
✓ Branch 40 → 41 taken 3 times.
✗ Branch 40 → 213 not taken.
✗ Branch 41 → 42 not taken.
✓ Branch 41 → 43 taken 3 times.
✗ Branch 42 → 43 not taken.
✗ Branch 42 → 46 not taken.
✓ Branch 43 → 44 taken 3 times.
✗ Branch 43 → 213 not taken.
✗ Branch 44 → 45 not taken.
✓ Branch 44 → 50 taken 3 times.
✗ Branch 45 → 46 not taken.
✗ Branch 45 → 50 not taken.
8 if (((vi.IsRGB32() && (rowsize % 16 == 0)) || (vi.IsRGB24() && (rowsize % 12 == 0)) || (vi.IsYUY2() && (rowsize % 16 == 0))) &&
2699
7/10
✓ Branch 38 → 39 taken 1 time.
✓ Branch 38 → 40 taken 3 times.
✓ Branch 46 → 47 taken 1 time.
✗ Branch 46 → 50 not taken.
✓ Branch 47 → 48 taken 1 time.
✗ Branch 47 → 213 not taken.
✓ Branch 48 → 49 taken 1 time.
✗ Branch 48 → 50 not taken.
✓ Branch 51 → 52 taken 1 time.
✓ Branch 51 → 53 taken 3 times.
8 (pixelsize == 1) && (env->GetCPUFlags() & CPUF_SSE2)) // only for uint8_t (pixelsize==1), todo
2700 {
2701
2702
1/2
✓ Branch 52 → 90 taken 1 time.
✗ Branch 52 → 213 not taken.
1 compare_sse2(mask, incr, f1ptr, pitch1, f2ptr, pitch2, rowsize, height, SAD, SD, pos_D, neg_D, SSD);
2703 }
2704 else
2705 #ifdef X86_32
2706 if (((vi.IsRGB32() && (rowsize % 8 == 0)) || (vi.IsRGB24() && (rowsize % 6 == 0)) || (vi.IsYUY2() && (rowsize % 8 == 0))) &&
2707 (pixelsize == 1) && (env->GetCPUFlags() & CPUF_INTEGER_SSE)) // only for uint8_t (pixelsize==1), todo
2708 {
2709 compare_isse(mask, incr, f1ptr, pitch1, f2ptr, pitch2, rowsize, height, SAD, SD, pos_D, neg_D, SSD);
2710 }
2711 else
2712 #endif
2713 #endif
2714 {
2715
2716
1/2
✗ Branch 53 → 54 not taken.
✓ Branch 53 → 55 taken 3 times.
3 if (pixelsize == 1)
2717 compare_c(mask, incr, f1ptr, pitch1, f2ptr, pitch2, rowsize, height, SAD, SD, pos_D, neg_D, SSD);
2718 else
2719
1/2
✓ Branch 55 → 90 taken 3 times.
✗ Branch 55 → 213 not taken.
3 compare_uint16_t_c(mask64, incr, f1ptr, pitch1, f2ptr, pitch2, rowsize, height, SAD_64, SD_64, pos_D, neg_D, SSD);
2720 }
2721 }
2722 else { // Planar
2723
2724 int planes_y[4] = { PLANAR_Y, PLANAR_U, PLANAR_V, PLANAR_A };
2725 int planes_r[4] = { PLANAR_G, PLANAR_B, PLANAR_R, PLANAR_A };
2726 int* planes = (vi.IsYUV() || vi.IsYUVA()) ? planes_y : planes_r;
2727 for (int p = 0; p < 4; p++) {
2728 const int plane = planes[p];
2729
2730 if (planar_plane & plane) {
2731
2732 const BYTE* f1ptr = f1->GetReadPtr(plane);
2733 const BYTE* f2ptr = f2->GetReadPtr(plane);
2734 const int pitch1 = f1->GetPitch(plane);
2735 const int pitch2 = f2->GetPitch(plane);
2736 const int rowsize = f1->GetRowSize(plane);
2737 const int height = f1->GetHeight(plane);
2738
2739 bytecount += (rowsize / pixelsize) * height;
2740 #ifdef INTEL_INTRINSICS
2741
2742 if ((pixelsize == 1) && (rowsize % 16 == 0) && (env->GetCPUFlags() & CPUF_SSE2))
2743 {
2744 compare_sse2(mask, incr, f1ptr, pitch1, f2ptr, pitch2, rowsize, height, SAD, SD, pos_D, neg_D, SSD);
2745 }
2746 else
2747 #ifdef X86_32
2748 if ((pixelsize == 1) && (rowsize % 8 == 0) && (env->GetCPUFlags() & CPUF_INTEGER_SSE))
2749 {
2750 compare_isse(mask, incr, f1ptr, pitch1, f2ptr, pitch2, rowsize, height, SAD, SD, pos_D, neg_D, SSD);
2751 }
2752 else
2753 #endif
2754 #endif
2755 {
2756
2757 if (pixelsize == 1)
2758 compare_planar_c(f1ptr, pitch1, f2ptr, pitch2, rowsize, height, SAD, SD, pos_D, neg_D, SSD);
2759 else
2760 compare_planar_uint16_t_c(f1ptr, pitch1, f2ptr, pitch2, rowsize, height, SAD_64, SD_64, pos_D, neg_D, SSD);
2761 }
2762 }
2763 }
2764 }
2765
2766
2/2
✓ Branch 90 → 91 taken 1 time.
✓ Branch 90 → 92 taken 3 times.
4 double MAD = ((pixelsize==1) ? (double)SAD : (double)SAD_64) / bytecount;
2767
2/2
✓ Branch 93 → 94 taken 1 time.
✓ Branch 93 → 95 taken 3 times.
4 double MD = ((pixelsize==1) ? (double)SD : (double)SD_64) / bytecount;
2768
1/2
✗ Branch 96 → 97 not taken.
✓ Branch 96 → 98 taken 4 times.
4 if (SSD == 0.0) SSD = 1.0;
2769 4 const int max_pixel_value = (1 << bits_per_pixel) - 1;
2770 4 double factor = (double)(max_pixel_value);
2771 4 double PSNR = 10.0 * log10(bytecount * factor * factor / SSD);
2772
2773 4 framecount++;
2774
1/2
✓ Branch 98 → 99 taken 4 times.
✗ Branch 98 → 100 not taken.
4 if (framecount == 1) {
2775 4 MAD_min = MAD_tot = MAD_max = MAD;
2776 4 MD_min = MD_tot = MD_max = MD;
2777 4 PSNR_min = PSNR_tot = PSNR_max = PSNR;
2778 4 bytecount_overall = double(bytecount);
2779 4 SSD_overall = SSD;
2780 } else {
2781 MAD_min = min(MAD_min, MAD);
2782 MAD_tot += MAD;
2783 MAD_max = max(MAD_max, MAD);
2784 MD_min = min(MD_min, MD);
2785 MD_tot += MD;
2786 MD_max = max(MD_max, MD);
2787 PSNR_min = min(PSNR_min, PSNR);
2788 PSNR_tot += PSNR;
2789 PSNR_max = max(PSNR_max, PSNR);
2790 bytecount_overall += double(bytecount);
2791 SSD_overall += SSD;
2792 }
2793
2794
1/2
✓ Branch 107 → 108 taken 4 times.
✗ Branch 107 → 111 not taken.
4 if (log) {
2795
2/2
✓ Branch 108 → 109 taken 1 time.
✓ Branch 108 → 110 taken 3 times.
4 if (pixelsize == 1)
2796
1/2
✓ Branch 109 → 205 taken 1 time.
✗ Branch 109 → 213 not taken.
1 fprintf(log,"%6u %8.4f %+9.4f %3d %3d %8.4f\n", (unsigned int)n, MAD, MD, pos_D, neg_D, PSNR);
2797 else
2798
1/2
✓ Branch 110 → 205 taken 3 times.
✗ Branch 110 → 213 not taken.
3 fprintf(log,"%6u %11.4f %+12.4f %7d %7d %8.4f\n", (unsigned int)n, MAD, MD, pos_D, neg_D, PSNR);
2799 } else {
2800 env->MakeWritable(&f1);
2801 BYTE* dstp = f1->GetWritePtr();
2802 int dst_pitch = f1->GetPitch();
2803
2804 #if defined(AVS_WINDOWS) && !defined(NO_WIN_GDI)
2805 HDC hdc = nullptr;
2806 if (use_gdi)
2807 hdc = antialiaser->GetDC();
2808 if (use_gdi ? hdc != nullptr : current_font != nullptr)
2809 #else
2810 if (current_font != nullptr)
2811 #endif
2812 {
2813 char text[600];
2814 double PSNR_overall = 10.0 * log10(bytecount_overall * factor * factor / SSD_overall);
2815 if (pixelsize == 1)
2816 snprintf(text, sizeof(text),
2817 " Frame: %-8u( min / avg / max )\n"
2818 "Mean Abs Dev:%8.4f (%7.3f /%7.3f /%7.3f )\n"
2819 " Mean Dev:%+8.4f (%+7.3f /%+7.3f /%+7.3f )\n"
2820 " Max Pos Dev:%4d \n"
2821 " Max Neg Dev:%4d \n"
2822 " PSNR:%6.2f dB ( %6.2f / %6.2f / %6.2f )\n"
2823 "Overall PSNR:%6.2f dB\n",
2824 n,
2825 MAD, MAD_min, MAD_tot / framecount, MD_max,
2826 MD, MD_min, MD_tot / framecount, MD_max,
2827 pos_D,
2828 neg_D,
2829 PSNR, PSNR_min, PSNR_tot / framecount, PSNR_max,
2830 PSNR_overall
2831 );
2832 else
2833 snprintf(text, sizeof(text),
2834 " Frame: %-8u ( min / avg / max )\n"
2835 "Mean Abs Dev:%11.4f (%10.3f /%10.3f /%10.3f )\n"
2836 " Mean Dev:%+11.4f (%+10.3f /%+10.3f /%+10.3f )\n"
2837 " Max Pos Dev:%7d \n"
2838 " Max Neg Dev:%7d \n"
2839 " PSNR:%6.2f dB ( %6.2f / %6.2f / %6.2f )\n"
2840 "Overall PSNR:%6.2f dB\n",
2841 n,
2842 MAD, MAD_min, MAD_tot / framecount, MD_max,
2843 MD, MD_min, MD_tot / framecount, MD_max,
2844 pos_D,
2845 neg_D,
2846 PSNR, PSNR_min, PSNR_tot / framecount, PSNR_max,
2847 PSNR_overall
2848 );
2849 #if defined(AVS_WINDOWS) && !defined(NO_WIN_GDI)
2850 if (use_gdi) {
2851 RECT r = { 32, 16, min((51+(pixelsize==1 ? 0: 12))*67, vi.width * 8), 768 + 128 }; // orig: 3440: 51*67, not enough for 16 bit data
2852 DrawText(hdc, text, -1, &r, 0);
2853 GdiFlush();
2854 antialiaser->Apply(vi, &f1, dst_pitch);
2855 } else {
2856 bool utf8 = true;
2857 auto s_utf8 = charToUtf8(text, utf8);
2858 SimpleTextOutW_multi(current_font.get(), vi, f1, 2, 1, s_utf8, true, text_color, halo_color, false, 0 /* no align */, 0 /*lsp*/, chromaplacement);
2859 }
2860 #else
2861 bool utf8 = true;
2862 auto s_utf8 = charToUtf8(text, utf8);
2863 SimpleTextOutW_multi(current_font.get(), vi, f1, 2, 1, s_utf8, true, text_color, halo_color, false, 0 /* no align */, 0 /*lsp*/, chromaplacement);
2864 #endif
2865 }
2866
2867 if (show_graph) {
2868 // original idea by Marc_FD
2869 // PF remark: show-graph (and file logging) is not for multitask
2870 // psnrs array is instance specific
2871 psnrs[n] = min((int)(PSNR + 0.5), 100);
2872 if (vi.height > 196) {
2873 if (vi.IsYUY2()) {
2874 dstp += (vi.height - 1) * dst_pitch;
2875 for (int y = 0; y <= 100; y++) {
2876 for (int x = max(0, vi.width - n - 1); x < vi.width; x++) {
2877 if (y <= psnrs[n - vi.width + 1 + x]) {
2878 if (y <= psnrs[n - vi.width + 1 + x] - 2) {
2879 dstp[x << 1] = 16; // Y
2880 dstp[((x & -1) << 1) + 1] = 0x80; // U
2881 dstp[((x & -1) << 1) + 3] = 0x80; // V
2882 } else {
2883 dstp[x << 1] = 235; // Y
2884 dstp[((x & -1) << 1) + 1] = 0x80; // U
2885 dstp[((x & -1) << 1) + 3] = 0x80; // V
2886 }
2887 }
2888 } // for x
2889 dstp -= dst_pitch;
2890 } // for y
2891 }
2892 else if (vi.IsPlanar()) {
2893 if (vi.IsPlanarRGB() || vi.IsPlanarRGBA())
2894 {
2895 BYTE* dstp_RGBP[3] = { f1->GetWritePtr(PLANAR_G), f1->GetWritePtr(PLANAR_B),f1->GetWritePtr(PLANAR_R) };
2896 int dst_pitch_RGBP[3] = { f1->GetPitch(PLANAR_G), f1->GetPitch(PLANAR_B), f1->GetPitch(PLANAR_R) };
2897
2898 dstp_RGBP[0] += (vi.height - 1) * dst_pitch_RGBP[0];
2899 dstp_RGBP[1] += (vi.height - 1) * dst_pitch_RGBP[1];
2900 dstp_RGBP[2] += (vi.height - 1) * dst_pitch_RGBP[2];
2901 for (int y = 0; y <= 100; y++) {
2902 for (int x = max(0, vi.width - n - 1); x < vi.width; x++) {
2903 if (y <= psnrs[n - vi.width + 1 + x]) {
2904 if (y <= psnrs[n - vi.width + 1 + x] - 2) {
2905 if(pixelsize==1) {
2906 dstp_RGBP[0][x] = 0;
2907 dstp_RGBP[1][x] = 0;
2908 dstp_RGBP[2][x] = 0;
2909 } else {
2910 reinterpret_cast<uint16_t *>(dstp_RGBP[0])[x] = 0;
2911 reinterpret_cast<uint16_t *>(dstp_RGBP[1])[x] = 0;
2912 reinterpret_cast<uint16_t *>(dstp_RGBP[2])[x] = 0;
2913 }
2914 } else {
2915 if(pixelsize==1) {
2916 dstp_RGBP[0][x] = 0xFF;
2917 dstp_RGBP[1][x] = 0xFF;
2918 dstp_RGBP[2][x] = 0xFF;
2919 } else {
2920 reinterpret_cast<uint16_t *>(dstp_RGBP[0])[x] = max_pixel_value;
2921 reinterpret_cast<uint16_t *>(dstp_RGBP[1])[x] = max_pixel_value;
2922 reinterpret_cast<uint16_t *>(dstp_RGBP[2])[x] = max_pixel_value;
2923 }
2924 }
2925 }
2926 } // for x
2927 dstp_RGBP[0] -= dst_pitch_RGBP[0];
2928 dstp_RGBP[1] -= dst_pitch_RGBP[1];
2929 dstp_RGBP[2] -= dst_pitch_RGBP[2];
2930 }
2931 } else {
2932 // planar YUV
2933 dstp += (vi.height - 1) * dst_pitch;
2934 const int black = 16 << (bits_per_pixel - 8);
2935 const int white = 235 << (bits_per_pixel - 8);
2936 for (int y = 0; y <= 100; y++) {
2937 for (int x = max(0, vi.width - n - 1); x < vi.width; x++) {
2938 if (y <= psnrs[n - vi.width + 1 + x]) {
2939 if (y <= psnrs[n - vi.width + 1 + x] - 2) {
2940 if(pixelsize==1)
2941 dstp[x] = 16; // Y
2942 else
2943 reinterpret_cast<uint16_t *>(dstp)[x] = black; // Y
2944 } else {
2945 if(pixelsize==1)
2946 dstp[x] = 235; // Y
2947 else
2948 reinterpret_cast<uint16_t *>(dstp)[x] = white; // Y
2949 }
2950 }
2951 } // for x
2952 dstp -= dst_pitch;
2953 }
2954 } // for y
2955 } else { // packed RGB 8 or 16 bits
2956 for (int y = 0; y <= 100; y++) {
2957 for (int x = max(0, vi.width - n - 1); x < vi.width; x++) {
2958 if (y <= psnrs[n - vi.width + 1 + x]) {
2959 const int xx = x * incr;
2960 if (y <= psnrs[n - vi.width + 1 + x] -2) {
2961 if(pixelsize==1) {
2962 dstp[xx] = 0x00; // B
2963 dstp[xx + 1] = 0x00; // G
2964 dstp[xx + 2] = 0x00; // R
2965 }
2966 else {
2967 reinterpret_cast<uint16_t *>(dstp)[xx] = 0x00; // B
2968 reinterpret_cast<uint16_t *>(dstp)[xx + 1] = 0x00; // G
2969 reinterpret_cast<uint16_t *>(dstp)[xx + 2] = 0x00; // R
2970 }
2971 } else {
2972 if(pixelsize==1) {
2973 dstp[xx] = 0xFF; // B
2974 dstp[xx + 1] = 0xFF; // G
2975 dstp[xx + 2] = 0xFF; // R
2976 }
2977 else {
2978 reinterpret_cast<uint16_t *>(dstp)[xx] = 0xFFFF; // B
2979 reinterpret_cast<uint16_t *>(dstp)[xx + 1] = 0xFFFF; // G
2980 reinterpret_cast<uint16_t *>(dstp)[xx + 2] = 0xFFFF; // R
2981 }
2982 }
2983 }
2984 } // for x
2985 dstp += dst_pitch;
2986 } // for y
2987 } // RGB
2988 } // height > 100
2989 } // show_graph
2990 } // no logfile
2991
2992 4 return f1;
2993 4 }
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005 /************************************
3006 ******* Helper Functions ******
3007 ***********************************/
3008 #if defined(AVS_WINDOWS) && !defined(NO_WIN_GDI)
3009 bool GetTextBoundingBox(const char* text, const char* fontname, int size, bool bold,
3010 bool italic, int align, int* width, int* height, bool utf8)
3011 {
3012 HFONT hfont = LoadFont(fontname, size, bold, italic);
3013 if (hfont == NULL)
3014 return false;
3015 HDC hdc = GetDC(NULL);
3016 if (hdc == NULL)
3017 return false;
3018 HFONT hfontDefault = (HFONT)SelectObject(hdc, hfont);
3019 int old_map_mode = SetMapMode(hdc, MM_TEXT);
3020 UINT old_text_align = SetTextAlign(hdc, align);
3021
3022 // Initialize width and height with base value (8 GDI units)
3023 *height = 8;
3024 *width = 8;
3025
3026 bool success = true;
3027 const char* current_text_ptr = text;
3028
3029 // Handle empty string case immediately
3030 if (text == nullptr || *text == '\0') {
3031 // Already initialized to 8, so nothing more to do for empty text
3032 }
3033 else {
3034 // Loop through lines
3035
3036 while (*current_text_ptr != '\0')
3037 {
3038 // Find the end of the current line segment (first CR or LF)
3039 const char* line_end_ptr = current_text_ptr;
3040 while (*line_end_ptr != '\0' && *line_end_ptr != '\r' && *line_end_ptr != '\n') {
3041 line_end_ptr++;
3042 }
3043
3044 const char* next_segment_start;
3045 std::unique_ptr<wchar_t[]> wide_line; // Used for converting a substring
3046 std::string temp_line;
3047
3048 if (*line_end_ptr != '\0') // Found a line break character
3049 {
3050 // Extract the current line segment
3051 temp_line.assign(current_text_ptr, (size_t)(line_end_ptr - current_text_ptr));
3052 wide_line = utf8 ? Utf8ToWideChar(temp_line.c_str()) : AnsiToWideChar(temp_line.c_str());
3053
3054 // Advance pointer past the line break(s)
3055 next_segment_start = line_end_ptr;
3056 if (*next_segment_start == '\r' && *(next_segment_start + 1) == '\n') {
3057 next_segment_start += 2;
3058 }
3059 else {
3060 next_segment_start += 1;
3061 }
3062 }
3063 else // End of string
3064 {
3065 // Process the rest of the string as the last line
3066 wide_line = utf8 ? Utf8ToWideChar(current_text_ptr) : AnsiToWideChar(current_text_ptr);
3067 next_segment_start = current_text_ptr + strlen(current_text_ptr);
3068 }
3069
3070 if (!wide_line) {
3071 success = false;
3072 break;
3073 }
3074
3075 RECT r = { 0, 0, 0, 0 };
3076 // Use DrawTextW for wide characters with DT_CALCRECT and DT_SINGLELINE
3077 success = (DrawTextW(hdc, wide_line.get(), (int)(wcslen(wide_line.get())), &r, DT_CALCRECT | DT_NOPREFIX | DT_SINGLELINE) != 0);
3078
3079 if (!success)
3080 break;
3081
3082 // +8 GDI units to the right side (width) as well
3083 *width = std::max(*width, (int)r.right + 8); // Update max width encountered
3084 *height += r.bottom; // Add this line's height
3085
3086 if (*next_segment_start != '\0')
3087 current_text_ptr = next_segment_start;// Move past the newline character
3088 else
3089 break; // No more newlines, end of text
3090 }
3091 }
3092
3093 // Clean up GDI objects
3094 SetTextAlign(hdc, old_text_align);
3095 SetMapMode(hdc, old_map_mode);
3096 SelectObject(hdc, hfontDefault);
3097 DeleteObject(hfont);
3098 ReleaseDC(NULL, hdc);
3099
3100 return success;
3101 }
3102 #endif
3103
3104 bool GetTextBoundingBoxFixed(const char* text, const char* fontname, int size, bool bold,
3105 bool italic, int align, int& width, int& height, bool utf8)
3106 {
3107 std::unique_ptr<BitmapFont> current_font;
3108 /*
3109 if (*font_filename) {
3110 // external font file
3111 const bool debugSave = false;
3112 current_font = GetBitmapFont(0, font_filename, false, debugSave); // 0: size n/a
3113 if (current_font == nullptr)
3114 env->ThrowError("SimpleText: file %s not found or unknown file format", font_filename);
3115 }
3116 else
3117 */
3118 {
3119 // internal font
3120 if (fontname) {
3121 current_font = GetBitmapFont(size, fontname, bold, false); // 12, "Terminus"
3122 if (current_font == nullptr)
3123 return false; // ("internal font name %s in size %d not found", fontname, size
3124 }
3125 else {
3126 // size
3127 current_font = GetBitmapFont(size, "", bold, false); // 12, ""
3128 if (current_font == nullptr)
3129 return false; // "fixed font size %d not found", size
3130 }
3131 }
3132
3133 size_t max_width = 1;
3134 height = 1;
3135
3136 // make list governed by LF separator
3137 std::string temp;
3138 std::stringstream ss(text);
3139 while (std::getline(ss, temp, '\n')) {
3140 // does not recognize combined unicode sequences,
3141 // e.g. U: is len=2 and not len=1 like Ü
3142
3143 // We no longer assume fixed width for all chars in BDF, must calculate width by adding each characters' real width
3144 // max_width = std::max(max_width, real_len * current_font->global_bbx.width);
3145 // cannot do simple len * FONT_WIDTH, because characters can have different widths
3146
3147 std::string s_utf8 = charToUtf8(temp.c_str(), utf8);
3148 // map an utf8 string to a sequence of character map indexes
3149 auto s_remapped = current_font->remap(s_utf8); // array of font table indexes
3150 size_t total_width = 0;
3151 for (int i = 0; i < (int)s_remapped.size(); i++) {
3152 // bbx_array is the array of bounding boxes for each character
3153 // s[i] is the index to the fontbitmap array
3154 total_width += current_font->bbx_array[s_remapped[i]].width;
3155 }
3156 max_width = std::max(max_width, total_width);
3157 height += current_font->global_bbx.height;
3158 }
3159
3160 width = (int)max_width;
3161
3162 return true;
3163 }
3164
3165 // old ApplyMessage with an extra utf8 parameter
3166 1 void ApplyMessageEx(PVideoFrame* frame, const VideoInfo& vi, const char* message, int size,
3167 int textcolor, int halocolor, int bgcolor, bool utf8, IScriptEnvironment* env)
3168 {
3169 AVS_UNUSED(bgcolor);
3170 AVS_UNUSED(env);
3171
5/10
✓ Branch 2 → 3 taken 1 time.
✗ Branch 2 → 54 not taken.
✓ Branch 3 → 4 taken 1 time.
✗ Branch 3 → 6 not taken.
✓ Branch 4 → 5 taken 1 time.
✗ Branch 4 → 54 not taken.
✗ Branch 5 → 6 not taken.
✓ Branch 5 → 7 taken 1 time.
✗ Branch 8 → 9 not taken.
✓ Branch 8 → 12 taken 1 time.
1 if (vi.IsYUV() || vi.IsYUVA()) {
3172 textcolor = RGB2YUV_Rec601(textcolor);
3173 halocolor = RGB2YUV_Rec601(halocolor);
3174 }
3175
3176 1 const bool bold = true;
3177 1 const bool italic = false;
3178 1 const bool noaa = false;
3179
3180 int chromaplacement;
3181
3182 // SD: Up to 720x576 (PAL/NTSC standard definition)
3183
1/4
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 14 taken 1 time.
✗ Branch 13 → 14 not taken.
✗ Branch 13 → 15 not taken.
1 if (vi.width <= 720 || vi.height <= 576) {
3184 1 chromaplacement = ChromaLocation_e::AVS_CHROMA_CENTER;
3185 }
3186 // HD: Up to 1920x1080 (Full HD)
3187 else if (vi.width <= 1920 || vi.height <= 1080) {
3188 chromaplacement = ChromaLocation_e::AVS_CHROMA_LEFT;
3189 }
3190 // UHD / 4K and above
3191 else {
3192 chromaplacement = ChromaLocation_e::AVS_CHROMA_TOP_LEFT;
3193 }
3194
3195 #if defined(AVS_WINDOWS) && !defined(NO_WIN_GDI)
3196 const int64_t cpuFlags = env->GetCPUFlagsEx();
3197 Antialiaser antialiaser(vi.width, vi.height, "Arial", size, textcolor, halocolor, bold, italic, noaa, cpuFlags, chromaplacement);
3198 HDC hdcAntialias = antialiaser.GetDC();
3199 if (hdcAntialias)
3200 {
3201 RECT r = { 4 * 8, 4 * 8, vi.width * 8, vi.height * 8 };
3202
3203 auto utf8Message = utf8 ? Utf8ToWideChar(message) : AnsiToWideChar(message);
3204 DrawTextW(hdcAntialias, utf8Message.get(), (int)wcslen(utf8Message.get()), &r, DT_NOPREFIX | DT_CENTER);
3205
3206 GdiFlush();
3207 antialiaser.Apply(vi, frame, (*frame)->GetPitch());
3208 }
3209 #else
3210 1 std::unique_ptr<BitmapFont> current_font;
3211
3212 1 size = size / 8; // size comes in GDI units (*8)
3213
3214 // internal font
3215
1/2
✓ Branch 19 → 20 taken 1 time.
✗ Branch 19 → 47 not taken.
1 current_font = GetBitmapFont(size, "Terminus", bold, false);
3216
1/2
✗ Branch 23 → 24 not taken.
✓ Branch 23 → 36 taken 1 time.
1 if (current_font == nullptr)
3217 {
3218 // size
3219 current_font = GetBitmapFont(size, "", bold, false);
3220 if (current_font == nullptr)
3221 current_font = GetBitmapFont(size, "", !bold, false);
3222 if (current_font == nullptr)
3223 return;
3224 }
3225
3226 // AVS_POSIX: utf8 is always true
3227
1/2
✓ Branch 36 → 37 taken 1 time.
✗ Branch 36 → 52 not taken.
1 std::string s_utf8 = charToUtf8(message, utf8);
3228
3229 1 int align = 7;
3230 1 int lsp = 0;
3231 1 int x = 4;
3232 1 int y = 4;
3233
3234
1/2
✓ Branch 38 → 39 taken 1 time.
✗ Branch 38 → 50 not taken.
1 SimpleTextOutW_multi(current_font.get(), vi, *frame, x, y, s_utf8, false, textcolor, halocolor, true, align, lsp, chromaplacement);
3235
3236 #endif
3237
1/2
✓ Branch 42 → 43 taken 1 time.
✗ Branch 42 → 45 not taken.
1 }
3238
3239 void ApplyMessage(PVideoFrame* frame, const VideoInfo& vi, const char* message, int size,
3240 int textcolor, int halocolor, int bgcolor, IScriptEnvironment* env) {
3241 // Simply call ApplyMessageEx with utf8=false
3242 ApplyMessageEx(frame, vi, message, size, textcolor, halocolor, bgcolor, false /*utf8*/, env);
3243 }
3244
3245