1 /*
2 * Copyright (c) 2017 Fredrik Mellbin
3 *
4 * This file is part of VapourSynth.
5 *
6 * VapourSynth is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * VapourSynth is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with VapourSynth; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 #include "internalfilters.h"
22 #include "VSHelper.h"
23 #include "filtershared.h"
24 #include "filtersharedcpp.h"
25
26 #include <memory>
27 #include <algorithm>
28 #include <stdexcept>
29 #include <string>
30 #include <vector>
31
32 namespace {
operator ""_s(const char * str,size_t len)33 std::string operator""_s(const char *str, size_t len) { return{ str, len }; }
34 } // namespace
35
36 //////////////////////////////////////////
37 // BoxBlur
38
39 struct BoxBlurData {
40 VSNodeRef *node;
41 int radius, passes;
42 };
43
44 template<typename T>
blurH(const T * VS_RESTRICT src,T * VS_RESTRICT dst,const int width,const int radius,const unsigned div,const unsigned round)45 static void blurH(const T * VS_RESTRICT src, T * VS_RESTRICT dst, const int width, const int radius, const unsigned div, const unsigned round) {
46 unsigned acc = radius * src[0];
47 for (int x = 0; x < radius; x++)
48 acc += src[std::min(x, width - 1)];
49
50 for (int x = 0; x < std::min(radius, width); x++) {
51 acc += src[std::min(x + radius, width - 1)];
52 dst[x] = (acc + round) / div;
53 acc -= src[std::max(x - radius, 0)];
54 }
55
56 if (width > radius) {
57 for (int x = radius; x < width - radius; x++) {
58 acc += src[x + radius];
59 dst[x] = (acc + round) / div;
60 acc -= src[x - radius];
61 }
62
63 for (int x = std::max(width - radius, radius); x < width; x++) {
64 acc += src[std::min(x + radius, width - 1)];
65 dst[x] = (acc + round) / div;
66 acc -= src[std::max(x - radius, 0)];
67 }
68 }
69 }
70
71 template<typename T>
processPlane(const uint8_t * src,uint8_t * dst,int stride,int width,int height,int passes,int radius,uint8_t * tmp)72 static void processPlane(const uint8_t *src, uint8_t *dst, int stride, int width, int height, int passes, int radius, uint8_t *tmp) {
73 const unsigned div = radius * 2 + 1;
74 const unsigned round = div - 1;
75 for (int h = 0; h < height; h++) {
76 uint8_t *dst1 = (passes & 1) ? dst : tmp;
77 uint8_t *dst2 = (passes & 1) ? tmp : dst;
78 blurH(reinterpret_cast<const T *>(src), reinterpret_cast<T *>(dst1), width, radius, div, round);
79 for (int p = 1; p < passes; p++) {
80 blurH(reinterpret_cast<const T *>(dst1), reinterpret_cast<T *>(dst2), width, radius, div, (p & 1) ? 0 : round);
81 std::swap(dst1, dst2);
82 }
83 src += stride;
84 dst += stride;
85 }
86 }
87
88 template<typename T>
blurHF(const T * VS_RESTRICT src,T * VS_RESTRICT dst,const int width,const int radius,const T div)89 static void blurHF(const T * VS_RESTRICT src, T * VS_RESTRICT dst, const int width, const int radius, const T div) {
90 T acc = radius * src[0];
91 for (int x = 0; x < radius; x++)
92 acc += src[std::min(x, width - 1)];
93
94 for (int x = 0; x < std::min(radius, width); x++) {
95 acc += src[std::min(x + radius, width - 1)];
96 dst[x] = acc * div;
97 acc -= src[std::max(x - radius, 0)];
98 }
99
100 if (width > radius) {
101 for (int x = radius; x < width - radius; x++) {
102 acc += src[x + radius];
103 dst[x] = acc * div;
104 acc -= src[x - radius];
105 }
106
107 for (int x = std::max(width - radius, radius); x < width; x++) {
108 acc += src[std::min(x + radius, width - 1)];
109 dst[x] = acc * div;
110 acc -= src[std::max(x - radius, 0)];
111 }
112 }
113 }
114
115 template<typename T>
processPlaneF(const uint8_t * src,uint8_t * dst,int stride,int width,int height,int passes,int radius,uint8_t * tmp)116 static void processPlaneF(const uint8_t *src, uint8_t *dst, int stride, int width, int height, int passes, int radius, uint8_t *tmp) {
117 const T div = static_cast<T>(1) / (radius * 2 + 1);
118 for (int h = 0; h < height; h++) {
119 uint8_t *dst1 = (passes & 1) ? dst : tmp;
120 uint8_t *dst2 = (passes & 1) ? tmp : dst;
121 blurHF(reinterpret_cast<const T *>(src), reinterpret_cast<T *>(dst1), width, radius, div);
122 for (int p = 1; p < passes; p++) {
123 blurHF(reinterpret_cast<const T *>(dst1), reinterpret_cast<T *>(dst2), width, radius, div);
124 std::swap(dst1, dst2);
125 }
126 src += stride;
127 dst += stride;
128 }
129 }
130
131 template<typename T>
blurHR1(const T * src,T * dst,int width,const unsigned round)132 static void blurHR1(const T *src, T *dst, int width, const unsigned round) {
133 unsigned tmp[2] = { src[0], src[1] };
134 unsigned acc = tmp[0] * 2 + tmp[1];
135 dst[0] = (acc + round) / 3;
136 acc -= tmp[0];
137
138 unsigned v = src[2];
139 acc += v;
140 dst[1] = (acc + round) / 3;
141 acc -= tmp[0];
142 tmp[0] = v;
143
144 for (int x = 2; x < width - 2; x += 2) {
145 v = src[x + 1];
146 acc += v;
147 dst[x] = (acc + round) / 3;
148 acc -= tmp[1];
149 tmp[1] = v;
150
151 v = src[x + 2];
152 acc += v;
153 dst[x + 1] = (acc + round) / 3;
154 acc -= tmp[0];
155 tmp[0] = v;
156 }
157
158 if (width & 1) {
159 acc += tmp[0];
160 dst[width - 1] = (acc + round) / 3;
161 } else {
162 v = src[width - 1];
163 acc += v;
164 dst[width - 2] = (acc + round) / 3;
165 acc -= tmp[1];
166
167 acc += v;
168 dst[width - 1] = (acc + round) / 3;
169 }
170 }
171
172 template<typename T>
processPlaneR1(const uint8_t * src,uint8_t * dst,int stride,int width,int height,int passes)173 static void processPlaneR1(const uint8_t *src, uint8_t *dst, int stride, int width, int height, int passes) {
174 for (int h = 0; h < height; h++) {
175 blurHR1(reinterpret_cast<const T *>(src), reinterpret_cast<T *>(dst), width, 2);
176 for (int p = 1; p < passes; p++)
177 blurHR1(reinterpret_cast<const T *>(dst), reinterpret_cast<T *>(dst), width, (p & 1) ? 0 : 2);
178 src += stride;
179 dst += stride;
180 }
181 }
182
183 template<typename T>
blurHR1F(const T * src,T * dst,int width)184 static void blurHR1F(const T *src, T *dst, int width) {
185 T tmp[2] = { src[0], src[1] };
186 T acc = tmp[0] * 2 + tmp[1];
187 const T div = static_cast<T>(1) / 3;
188 dst[0] = acc * div;
189 acc -= tmp[0];
190
191 T v = src[2];
192 acc += v;
193 dst[1] = acc * div;
194 acc -= tmp[0];
195 tmp[0] = v;
196
197 for (int x = 2; x < width - 2; x += 2) {
198 v = src[x + 1];
199 acc += v;
200 dst[x] = acc * div;
201 acc -= tmp[1];
202 tmp[1] = v;
203
204 v = src[x + 2];
205 acc += v;
206 dst[x + 1] = acc * div;
207 acc -= tmp[0];
208 tmp[0] = v;
209 }
210
211 if (width & 1) {
212 acc += tmp[0];
213 dst[width - 1] = acc * div;
214 }
215 else {
216 v = src[width - 1];
217 acc += v;
218 dst[width - 2] = acc * div;
219 acc -= tmp[1];
220
221 acc += v;
222 dst[width - 1] = acc * div;
223 }
224 }
225
226 template<typename T>
processPlaneR1F(const uint8_t * src,uint8_t * dst,int stride,int width,int height,int passes)227 static void processPlaneR1F(const uint8_t *src, uint8_t *dst, int stride, int width, int height, int passes) {
228 for (int h = 0; h < height; h++) {
229 blurHR1F(reinterpret_cast<const T *>(src), reinterpret_cast<T *>(dst), width);
230 for (int p = 1; p < passes; p++)
231 blurHR1F(reinterpret_cast<const T *>(dst), reinterpret_cast<T *>(dst), width);
232 src += stride;
233 dst += stride;
234 }
235 }
236
boxBlurGetframe(int n,int activationReason,void ** instanceData,void ** frameData,VSFrameContext * frameCtx,VSCore * core,const VSAPI * vsapi)237 static const VSFrameRef *VS_CC boxBlurGetframe(int n, int activationReason, void **instanceData, void **frameData, VSFrameContext *frameCtx, VSCore *core, const VSAPI *vsapi) {
238 BoxBlurData *d = reinterpret_cast<BoxBlurData *>(*instanceData);
239
240 if (activationReason == arInitial) {
241 vsapi->requestFrameFilter(n, d->node, frameCtx);
242 } else if (activationReason == arAllFramesReady) {
243 const VSFrameRef *src = vsapi->getFrameFilter(n, d->node, frameCtx);
244 const VSFormat *fi = vsapi->getFrameFormat(src);
245 VSFrameRef *dst = vsapi->newVideoFrame(fi, vsapi->getFrameWidth(src, 0), vsapi->getFrameHeight(src, 0), src, core);
246 int bytesPerSample = fi->bytesPerSample;
247 int radius = d->radius;
248 uint8_t *tmp = (radius > 1 && d->passes > 1) ? new uint8_t[bytesPerSample * vsapi->getFrameWidth(src, 0)] : nullptr;
249
250 const uint8_t *srcp = vsapi->getReadPtr(src, 0);
251 int stride = vsapi->getStride(src, 0);
252 uint8_t *dstp = vsapi->getWritePtr(dst, 0);
253 int h = vsapi->getFrameHeight(src, 0);
254 int w = vsapi->getFrameWidth(src, 0);
255
256 if (radius == 1) {
257 if (bytesPerSample == 1)
258 processPlaneR1<uint8_t>(srcp, dstp, stride, w, h, d->passes);
259 else if (bytesPerSample == 2)
260 processPlaneR1<uint16_t>(srcp, dstp, stride, w, h, d->passes);
261 else
262 processPlaneR1F<float>(srcp, dstp, stride, w, h, d->passes);
263 } else {
264 if (bytesPerSample == 1)
265 processPlane<uint8_t>(srcp, dstp, stride, w, h, d->passes, radius, tmp);
266 else if (bytesPerSample == 2)
267 processPlane<uint16_t>(srcp, dstp, stride, w, h, d->passes, radius, tmp);
268 else
269 processPlaneF<float>(srcp, dstp, stride, w, h, d->passes, radius, tmp);
270 }
271
272 delete[] tmp;
273
274 vsapi->freeFrame(src);
275 return dst;
276 }
277
278 return nullptr;
279 }
280
applyBoxBlurPlaneFiltering(VSPlugin * stdplugin,VSNodeRef * node,int hradius,int hpasses,int vradius,int vpasses,VSCore * core,const VSAPI * vsapi)281 static VSNodeRef *applyBoxBlurPlaneFiltering(VSPlugin *stdplugin, VSNodeRef *node, int hradius, int hpasses, int vradius, int vpasses, VSCore *core, const VSAPI *vsapi) {
282 bool hblur = (hradius > 0) && (hpasses > 0);
283 bool vblur = (vradius > 0) && (vpasses > 0);
284
285 if (hblur) {
286 VSMap *vtmp1 = vsapi->createMap();
287 VSMap *vtmp2 = vsapi->createMap();
288 vsapi->createFilter(vtmp1, vtmp2, "BoxBlur", templateNodeInit<BoxBlurData>, boxBlurGetframe, templateNodeFree<BoxBlurData>, fmParallel, 0, new BoxBlurData{ node, hradius, hpasses }, core);
289 node = vsapi->propGetNode(vtmp2, "clip", 0, nullptr);
290 vsapi->freeMap(vtmp1);
291 vsapi->freeMap(vtmp2);
292 }
293
294 if (vblur) {
295 VSMap *vtmp1 = vsapi->createMap();
296 vsapi->propSetNode(vtmp1, "clip", node, paAppend);
297 vsapi->freeNode(node);
298 VSMap *vtmp2 = vsapi->invoke(stdplugin, "Transpose", vtmp1);
299 vsapi->clearMap(vtmp1);
300 node = vsapi->propGetNode(vtmp2, "clip", 0, nullptr);
301 vsapi->clearMap(vtmp2);
302 vsapi->createFilter(vtmp1, vtmp2, "BoxBlur", templateNodeInit<BoxBlurData>, boxBlurGetframe, templateNodeFree<BoxBlurData>, fmParallel, 0, new BoxBlurData{ node, vradius, vpasses }, core);
303 vsapi->freeMap(vtmp1);
304 vtmp1 = vsapi->invoke(stdplugin, "Transpose", vtmp2);
305 vsapi->freeMap(vtmp2);
306 node = vsapi->propGetNode(vtmp1, "clip", 0, nullptr);
307 vsapi->freeMap(vtmp1);
308 }
309
310 return node;
311 }
312
boxBlurCreate(const VSMap * in,VSMap * out,void * userData,VSCore * core,const VSAPI * vsapi)313 static void VS_CC boxBlurCreate(const VSMap *in, VSMap *out, void *userData, VSCore *core, const VSAPI *vsapi) {
314 VSNodeRef *node = vsapi->propGetNode(in, "clip", 0, 0);
315
316 try {
317 int err;
318 const VSVideoInfo *vi = vsapi->getVideoInfo(node);
319
320 shared816FFormatCheck(vi->format);
321
322 bool process[3];
323 getPlanesArg(in, process, vsapi);
324
325 int hradius = int64ToIntS(vsapi->propGetInt(in, "hradius", 0, &err));
326 if (err)
327 hradius = 1;
328 int hpasses = int64ToIntS(vsapi->propGetInt(in, "hpasses", 0, &err));
329 if (err)
330 hpasses = 1;
331 bool hblur = (hradius > 0) && (hpasses > 0);
332
333 int vradius = int64ToIntS(vsapi->propGetInt(in, "vradius", 0, &err));
334 if (err)
335 vradius = 1;
336 int vpasses = int64ToIntS(vsapi->propGetInt(in, "vpasses", 0, &err));
337 if (err)
338 vpasses = 1;
339 bool vblur = (vradius > 0) && (vpasses > 0);
340
341 if (hpasses < 0 || vpasses < 0)
342 throw std::runtime_error("number of passes can't be negative");
343
344 if (hradius < 0 || vradius < 0)
345 throw std::runtime_error("radius can't be negative");
346
347 if (hradius > 30000 || vradius > 30000)
348 throw std::runtime_error("radius must be less than 30000");
349
350 if (!hblur && !vblur)
351 throw std::runtime_error("nothing to be performed");
352
353 VSPlugin *stdplugin = vsapi->getPluginById("com.vapoursynth.std", core);
354
355 if (vi->format->numPlanes == 1) {
356 VSNodeRef *tmpnode = applyBoxBlurPlaneFiltering(stdplugin, node, hradius, hpasses, vradius, vpasses, core, vsapi);
357 node = nullptr;
358 vsapi->propSetNode(out, "clip", tmpnode, paAppend);
359 vsapi->freeNode(tmpnode);
360 } else {
361 VSMap *mergeargs = vsapi->createMap();
362 int64_t psrc[3] = { 0, process[1] ? 0 : 1, process[2] ? 0 : 2 };
363 vsapi->propSetIntArray(mergeargs, "planes", psrc, 3);
364 vsapi->propSetInt(mergeargs, "colorfamily", vi->format->colorFamily, paAppend);
365
366 for (int plane = 0; plane < vi->format->numPlanes; plane++) {
367 if (process[plane]) {
368 VSMap *vtmp1 = vsapi->createMap();
369 vsapi->propSetNode(vtmp1, "clips", node, paAppend);
370 vsapi->propSetInt(vtmp1, "planes", plane, paAppend);
371 vsapi->propSetInt(vtmp1, "colorfamily", cmGray, paAppend);
372 VSMap *vtmp2 = vsapi->invoke(stdplugin, "ShufflePlanes", vtmp1);
373 vsapi->freeMap(vtmp1);
374 VSNodeRef *tmpnode = vsapi->propGetNode(vtmp2, "clip", 0, nullptr);
375 vsapi->freeMap(vtmp2);
376 tmpnode = applyBoxBlurPlaneFiltering(stdplugin, tmpnode, hradius, hpasses, vradius, vpasses, core, vsapi);
377 vsapi->propSetNode(mergeargs, "clips", tmpnode, paAppend);
378 vsapi->freeNode(tmpnode);
379 } else {
380 vsapi->propSetNode(mergeargs, "clips", node, paAppend);
381 }
382 }
383
384 vsapi->freeNode(node);
385 node = nullptr;
386
387 VSMap *retmap = vsapi->invoke(stdplugin, "ShufflePlanes", mergeargs);
388 vsapi->freeMap(mergeargs);
389 VSNodeRef *tmpnode = vsapi->propGetNode(retmap, "clip", 0, nullptr);
390 vsapi->freeMap(retmap);
391 vsapi->propSetNode(out, "clip", tmpnode, paAppend);
392 vsapi->freeNode(tmpnode);
393 }
394
395 } catch (const std::exception &e) {
396 vsapi->freeNode(node);
397 RETERROR(("BoxBlur: "_s + e.what()).c_str());
398 }
399 }
400
401 //////////////////////////////////////////
402 // Init
403
boxBlurInitialize(VSConfigPlugin configFunc,VSRegisterFunction registerFunc,VSPlugin * plugin)404 void VS_CC boxBlurInitialize(VSConfigPlugin configFunc, VSRegisterFunction registerFunc, VSPlugin *plugin) {
405 //configFunc("com.vapoursynth.std", "std", "VapourSynth Core Functions", VAPOURSYNTH_API_VERSION, 1, plugin);
406 registerFunc("BoxBlur", "clip:clip;planes:int[]:opt;hradius:int:opt;hpasses:int:opt;vradius:int:opt;vpasses:int:opt;", boxBlurCreate, 0, plugin);
407 }
408