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