Log in

View Full Version : C++ resize sample


Ceppo
27th March 2022, 20:31
I would like to have a 8-bit bicubic resize sample, the problem is that I can't read optimizations stuff (I have no idea where to look anyway), since I want to try to mod bicubic resize and I don't know how to do a resize with avisynth :cool:

If someone can help :)

DTL
28th March 2022, 01:17
If you only want to use some modified kernel - you can use extracted AVS resampling engine in jpsdr's resize plugin. It is 1D+1D H+V classic resampling engine.
To test modified kernel - change existing bicubic (MitchellNetravaliFilter::MitchellNetravaliFilter (double b, double c)) or add new to https://github.com/jpsdr/plugins_JPSDR/blob/master/Plugins_JPSDR/resample_functions.cpp file and build.

If you need 2D 1pass resampling engine - it is JincResize plugin for example. https://github.com/Asd-g/AviSynth-JincResize

I think it is typically no need to change resampling engine already good optimized. Only if you want to try to put it to some unusual execution hardware for example.

Ceppo
28th March 2022, 10:16
Thanks, I will try your suggestion.

Ceppo
28th March 2022, 11:22
The code is almost the same as avisynth+ code and is a bit hard to read for my skill level, too much use of classes and so on which I'm not used to (still using good old void functions), I was looking for a simpler implementation if someone can do me this favor. :)

Reel.Deel
28th March 2022, 12:59
Would this be any good to you? Maybe all it's good for is study material since back in those days AviSynth only supported YUY2 and RGB.

resample_old.cpp.txt from the AviSynth 2.0 source.

/********************************************************************

The following is the original C++ code of the resize filters.
It is left here as documentation, because the assembler code is much
harder to understand.

********************************************************************

#include <math.h>

class ResamplingFunction {
public:
virtual double f(double x) = 0;
virtual double support() = 0;
};


class TriangleFilter : public ResamplingFunction {
public:
double f(double x) {
x = fabs(x);
return (x<1.0) ? 1.0-x : 0.0;
}
double support() { return 1.0; }
};


class MitchellNetravaliFilter : public ResamplingFunction {
double p0,p2,p3,q0,q1,q2,q3;
public:
MitchellNetravaliFilter(double b=1./3., double c=1./3.) {
p0 = ( 6. - 2.*b ) / 6.;
p2 = (-18. + 12.*b + 6.*c) / 6.;
p3 = ( 12. - 9.*b - 6.*c) / 6.;
q0 = ( 8.*b + 24.*c) / 6.;
q1 = ( - 12.*b - 48.*c) / 6.;
q2 = ( 6.*b + 30.*c) / 6.;
q3 = ( - b - 6.*c) / 6.;
}
double f(double x) {
x = fabs(x);
return (x<1) ? (p0+x*x*(p2+x*p3)) : (x<2) ? (q0+x*(q1+x*(q2+x*q3))) : 0.0;
}
double support() { return 2.0; }
};


// This function returns a resampling "program" which is interpreted by the
// FilteredResize filters. It handles edge conditions so FilteredResize
// doesn't have to.

static int* GetResamplingPattern(int original_width, double subrange_start, double subrange_width, int target_width, ResamplingFunction* func) {
double scale = double(target_width) / subrange_width;
double filter_step = min(scale, 1.0);
double filter_support = func->support() / filter_step;
int fir_filter_size = int(ceil(filter_support*2));
int* result = new int[1 + target_width*(1+fir_filter_size)];

int* cur = result;
*cur++ = fir_filter_size;

double pos_step = subrange_width / target_width;
// the following translates such that the image center remains fixed
double pos = subrange_start + ((subrange_width - target_width) / (target_width*2));

for (int i=0; i<target_width; ++i) {
int end_pos = int(pos + filter_support);
if (end_pos > original_width-1)
end_pos = original_width-1;
int start_pos = end_pos - fir_filter_size + 1;
if (start_pos < 0)
start_pos = 0;
*cur++ = start_pos;
// the following code ensures that the coefficients add to exactly 65536
double total = 0.0;
for (int j=0; j<fir_filter_size; ++j)
total += func->f((start_pos+j - pos) * filter_step);
double total2 = 0.0;
for (int k=0; k<fir_filter_size; ++k) {
double total3 = total2 + func->f((start_pos+k - pos) * filter_step) / total;
*cur++ = int(total3*65536+0.5) - int(total2*65536+0.5);
total2 = total3;
}
pos += pos_step;
}

return result;
}


class FilteredResizeH : public GenericVideoFilter {
int* pattern_luma;
int* pattern_chroma;
public:
FilteredResizeH(PClip _child, double subrange_left, double subrange_width, int target_width, ResamplingFunction* func, IScriptEnvironment* env)
: GenericVideoFilter(_child)
{
pattern_chroma = 0;
if (vi.IsYUY2()) {
if (target_width&1)
env->ThrowError("Resize: YUY2 width must be even");
pattern_chroma = GetResamplingPattern(vi.width>>1, subrange_left/2, subrange_width/2, target_width>>1, func);
}
pattern_luma = GetResamplingPattern(vi.width, subrange_left, subrange_width, target_width, func);
vi.width = target_width;
}

PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* env) {
PVideoFrame src = child->GetFrame(n, env);
PVideoFrame dst = env->NewVideoFrame(vi);
const BYTE* srcp = src->GetReadPtr();
BYTE* dstp = dst->GetWritePtr();
const int src_pitch = src->GetPitch();
const int dst_pitch = dst->GetPitch();
if (vi.IsYUY2()) {
for (int y=0; y<vi.height; ++y) {
int* cur = pattern_luma+1;
for (int x=0; x<vi.width; ++x) {
int total = 0;
int ofs = *cur++;
for (int a=0; a<pattern_luma[0]; ++a)
total += srcp[(ofs+a)*2] * (*cur++);
dstp[x*2] = ScaledPixelClip(total);
}
cur = pattern_chroma+1;
for (int xx=0; xx<vi.width; xx+=2) {
int utotal = 0, vtotal = 0;
int ofs = *cur++;
for (int a=0; a<pattern_chroma[0]; ++a) {
utotal += srcp[(ofs+a)*4+1] * (*cur);
vtotal += srcp[(ofs+a)*4+3] * (*cur);
++cur;
}
dstp[xx*2+1] = ScaledPixelClip(utotal);
dstp[xx*2+3] = ScaledPixelClip(vtotal);
}
srcp += src_pitch;
dstp += dst_pitch;
}
} else if (vi.IsRGB24()) {
for (int y=0; y<vi.height; ++y) {
int* cur = pattern_luma+1;
for (int x=0; x<vi.width; ++x) {
int btotal = 0, gtotal = 0, rtotal = 0;
int ofs = *cur++;
for (int a=0; a<pattern_luma[0]; ++a) {
btotal += srcp[(ofs+a)*3] * (*cur);
gtotal += srcp[(ofs+a)*3+1] * (*cur);
rtotal += srcp[(ofs+a)*3+2] * (*cur);
++cur;
}
dstp[x*3] = ScaledPixelClip(btotal);
dstp[x*3+1] = ScaledPixelClip(gtotal);
dstp[x*3+2] = ScaledPixelClip(rtotal);
}
srcp += src_pitch;
dstp += dst_pitch;
}
} else {
for (int y=0; y<vi.height; ++y) {
int* cur = pattern_luma+1;
for (int x=0; x<vi.width; ++x) {
int btotal = 0, gtotal = 0, rtotal = 0;
int ofs = *cur++;
for (int a=0; a<pattern_luma[0]; ++a) {
btotal += srcp[(ofs+a)*4] * (*cur);
gtotal += srcp[(ofs+a)*4+1] * (*cur);
rtotal += srcp[(ofs+a)*4+2] * (*cur);
++cur;
}
*(unsigned __int32*)&dstp[x*4] = ScaledPixelClip(btotal)
+ ScaledPixelClip(gtotal)*256 + ScaledPixelClip(rtotal) * 65536;
}
srcp += src_pitch;
dstp += dst_pitch;
}
}
return dst;
}

~FilteredResizeH() {
delete[] pattern_luma;
delete[] pattern_chroma;
}
};


class FilteredResizeV : public GenericVideoFilter {
int* resampling_pattern;
public:
FilteredResizeV(PClip _child, double subrange_top, double subrange_height, int target_height, ResamplingFunction* func)
: GenericVideoFilter(_child)
{
if (vi.IsRGB())
subrange_top = vi.height - subrange_top - subrange_height;
resampling_pattern = GetResamplingPattern(vi.height, subrange_top, subrange_height, target_height, func);
vi.height = target_height;
}

PVideoFrame __stdcall GetFrame(int n, IScriptEnvironment* env) {
PVideoFrame src = child->GetFrame(n, env);
PVideoFrame dst = env->NewVideoFrame(vi);
const int* cur = resampling_pattern;
const int fir_filter_width = *cur++;
const int src_pitch = src->GetPitch();
const int dst_pitch = dst->GetPitch();
const int row_size = src->GetRowSize();
BYTE* dstp = dst->GetWritePtr();
for (int y=0; y<vi.height; ++y) {
const BYTE* srcp = src->GetReadPtr() + src_pitch * (*cur++);
for (int x=0; x<row_size; ++x) {
int total = 0;
const BYTE* srcp2 = srcp+x;
for (int b=0; b<fir_filter_width; ++b) {
total += *srcp2 * cur[b];
srcp2 += src_pitch;
}
dstp[x] = ScaledPixelClip(total);
}
dstp += dst_pitch;
cur += fir_filter_width;
}
return dst;
}

~FilteredResizeV() { delete[] resampling_pattern; }
};


static PClip CreateResizeH(PClip clip, double subrange_left, double subrange_width, int target_width, ResamplingFunction* func, IScriptEnvironment* env) {
const VideoInfo& vi = clip->GetVideoInfo();
if (subrange_left == 0 && subrange_width == target_width && subrange_width == vi.width) {
return clip;
} else if (subrange_left == int(subrange_left) && subrange_width == target_width
&& subrange_left >= 0 && subrange_left + subrange_width <= vi.width) {
return new Crop(int(subrange_left), 0, int(subrange_width), vi.height, clip);
} else {
return new FilteredResizeH(clip, subrange_left, subrange_width, target_width, func, env);
}
}


static PClip CreateResizeV(PClip clip, double subrange_top, double subrange_height, int target_height, ResamplingFunction* func, IScriptEnvironment* env) {
const VideoInfo& vi = clip->GetVideoInfo();
if (subrange_top == 0 && subrange_height == target_height && subrange_height == vi.height) {
return clip;
} else if (subrange_top == int(subrange_top) && subrange_height == target_height
&& subrange_top >= 0 && subrange_top + subrange_height <= vi.height) {
return new Crop(0, int(subrange_top), vi.width, int(subrange_height), clip);
} else {
return new FilteredResizeV(clip, subrange_top, subrange_height, target_height, func);
}
}


static PClip CreateResize(PClip clip, int target_width, int target_height, const AVSValue* args, ResamplingFunction* f, IScriptEnvironment* env) {
const VideoInfo& vi = clip->GetVideoInfo();
const double subrange_left = args[0].AsFloat(0), subrange_top = args[1].AsFloat(0);
const double subrange_width = args[2].AsFloat(vi.width), subrange_height = args[3].AsFloat(vi.height);
return CreateResizeV(CreateResizeH(clip, subrange_left, subrange_width, target_width, f, env), subrange_top, subrange_height, target_height, f, env);
}


static AVSValue __cdecl Create_BilinearResize(AVSValue args, void*, IScriptEnvironment* env) {
return CreateResize(args[0].AsClip(), args[1].AsInt(), args[2].AsInt(), &args[3], &TriangleFilter(), env);
}

static AVSValue __cdecl Create_BicubicResize(AVSValue args, void*, IScriptEnvironment* env) {
return CreateResize(args[0].AsClip(), args[1].AsInt(), args[2].AsInt(), &args[5], &MitchellNetravaliFilter(args[3].AsFloat(1./3.), args[4].AsFloat(1./3.)), env);
}

*/

DTL
28th March 2022, 16:06
To change kernel filter function you need only change

double _filter_name::f(double x)

function. It must return 1 double float value and takes 1 double float argument. So you only need to understand how to scale kernel function of 1 argument. It even not need to be normalized. Not going into C++ with classes.

For example for bilinear it is as simple as

double TriangleFilter::f(double x)
{
x = fabs(x);
return (x<1.0) ? 1.0-x : 0.0;
}

If you change bicubic - you can clone its 'support' value. If you need wider kernel - adjust 'support' too. But it will not be bicubic more I think.

So for experiments you can change existing bicubic kernel function. If you got good result - may make it separated named copy like MyModifiedBicubicResize().

Ceppo
29th March 2022, 19:18
That is much more readable for my skill level.

Thanks, I will keep in mind your suggestions while I work on this idea. :)