#include "PtexPlatform.h"
#include "Ptexture.h"
#include "PtexMitchellFilter.h"
#include "PtexSeparableFilter.h"
#include "PtexSeparableKernel.h"
#include "PtexTriangleFilter.h"
namespace {
class PtexPointFilter : public PtexFilter, public Ptex
{
public:
PtexPointFilter(PtexTexture* tx) : _tx(tx) {}
virtual void release() { delete this; }
virtual void eval(float* result, int firstchan, int nchannels,
int faceid, float u, float v,
float , float , float , float ,
float , float )
{
if (!_tx || nchannels <= 0) return;
if (faceid < 0 || faceid >= _tx->numFaces()) return;
const FaceInfo& f = _tx->getFaceInfo(faceid);
int resu = f.res.u(), resv = f.res.v();
int ui = PtexUtils::clamp(int(u*resu), 0, resu-1);
int vi = PtexUtils::clamp(int(v*resv), 0, resv-1);
_tx->getPixel(faceid, ui, vi, result, firstchan, nchannels);
}
private:
PtexTexture* _tx;
};
class PtexPointFilterTri : public PtexFilter, public Ptex
{
public:
PtexPointFilterTri(PtexTexture* tx) : _tx(tx) {}
virtual void release() { delete this; }
virtual void eval(float* result, int firstchan, int nchannels,
int faceid, float u, float v,
float , float , float , float ,
float , float )
{
if (!_tx || nchannels <= 0) return;
if (faceid < 0 || faceid >= _tx->numFaces()) return;
const FaceInfo& f = _tx->getFaceInfo(faceid);
int res = f.res.u();
int resm1 = res - 1;
float ut = u * res, vt = v * res;
int ui = PtexUtils::clamp(int(ut), 0, resm1);
int vi = PtexUtils::clamp(int(vt), 0, resm1);
float uf = ut - ui, vf = vt - vi;
if (uf + vf <= 1.0) {
_tx->getPixel(faceid, ui, vi, result, firstchan, nchannels);
}
else {
_tx->getPixel(faceid, resm1-vi, resm1-ui, result, firstchan, nchannels);
}
}
private:
PtexTexture* _tx;
};
class PtexWidth4Filter : public PtexSeparableFilter
{
public:
typedef double KernelFn(double x, const double* c);
PtexWidth4Filter(PtexTexture* tx, const PtexFilter::Options& opts, KernelFn k, const double* c = 0)
: PtexSeparableFilter(tx, opts), _k(k), _c(c) {}
virtual void buildKernel(PtexSeparableKernel& k, float u, float v, float uw, float vw,
Res faceRes)
{
buildKernelAxis(k.res.ulog2, k.u, k.uw, k.ku, u, uw, faceRes.ulog2);
buildKernelAxis(k.res.vlog2, k.v, k.vw, k.kv, v, vw, faceRes.vlog2);
}
private:
double blur(double x)
{
x = fabs(x);
return x < 1 ? (2*x-3)*x*x+1 : 0;
}
void buildKernelAxis(int8_t& k_ureslog2, int& k_u, int& k_uw, double* ku,
float u, float uw, int f_ureslog2)
{
uw = PtexUtils::max(uw, 1.0f/(1<<f_ureslog2));
k_ureslog2 = int(ceil(log2(1.0/uw)));
int resu = 1 << k_ureslog2;
double uwlo = 1.0/resu;
double lerp2 = _options.lerp ? (uw-uwlo)/uwlo : 0;
double lerp1 = 1-lerp2;
if (uw >= .25) {
if (uw < .5) {
k_ureslog2 = 2;
double upix = u * 4 - 0.5;
int u1 = int(ceil(upix - 2)), u2 = int(ceil(upix + 2));
u1 = u1 & ~1;
u2 = (u2 + 1) & ~1;
k_u = u1;
k_uw = u2-u1;
double x1 = u1-upix;
for (int i = 0; i < k_uw; i+=2) {
double xa = x1 + i, xb = xa + 1, xc = (xa+xb)*0.25;
double s = 1.0/(uw + .75);
double ka = _k(xa, _c), kb = _k(xb, _c), kc = blur(xc*s);
ku[i] = ka * lerp1 + kc * lerp2;
ku[i+1] = kb * lerp1 + kc * lerp2;
}
return;
}
else if (uw < 1) {
k_ureslog2 = 1;
double upix = u * 2 - 0.5;
k_u = int(floor(u - .5))*2;
k_uw = 4;
double x1 = k_u-upix;
for (int i = 0; i < k_uw; i+=2) {
double xa = x1 + i, xb = xa + 1, xc = (xa+xb)*0.5;
double s = 1.0/(uw*1.5 + .5);
double ka = blur(xa*s), kb = blur(xb*s), kc = blur(xc*s);
ku[i] = ka * lerp1 + kc * lerp2;
ku[i+1] = kb * lerp1 + kc * lerp2;
}
return;
}
else {
k_ureslog2 = 0;
double upix = u - .5;
k_uw = 2;
double ui = floor(upix);
k_u = int(ui);
ku[0] = blur(upix-ui);
ku[1] = 1-ku[0];
return;
}
}
double upix = u * resu - 0.5;
double uwpix = uw * resu;
double dupix = 2*uwpix;
int u1 = int(ceil(upix - dupix)), u2 = int(ceil(upix + dupix));
if (lerp2) {
u1 = u1 & ~1;
u2 = (u2 + 1) & ~1;
k_u = u1;
k_uw = u2-u1;
double step = 1.0/uwpix, x1 = (u1-upix)*step;
for (int i = 0; i < k_uw; i+=2) {
double xa = x1 + i*step, xb = xa + step, xc = (xa+xb)*0.5;
double ka = _k(xa, _c), kb = _k(xb, _c), kc = _k(xc, _c);
ku[i] = ka * lerp1 + kc * lerp2;
ku[i+1] = kb * lerp1 + kc * lerp2;
}
}
else {
k_u = u1;
k_uw = u2-u1;
double x1 = (u1-upix)/uwpix, step = 1.0/uwpix;
for (int i = 0; i < k_uw; i++) ku[i] = _k(x1 + i*step, _c);
}
}
KernelFn* _k;
const double* _c;
};
class PtexBicubicFilter : public PtexWidth4Filter
{
public:
PtexBicubicFilter(PtexTexture* tx, const PtexFilter::Options& opts, float sharpness)
: PtexWidth4Filter(tx, opts, kernelFn, _coeffs)
{
float B = 1 - sharpness;
_coeffs[0] = 1.5 - B;
_coeffs[1] = 1.5 * B - 2.5;
_coeffs[2] = 1 - (1./3) * B;
_coeffs[3] = (1./3) * B - 0.5;
_coeffs[4] = 2.5 - 1.5 * B;
_coeffs[5] = 2 * B - 4;
_coeffs[6] = 2 - (2./3) * B;
}
private:
static double kernelFn(double x, const double* c)
{
x = fabs(x);
if (x < 1) return (c[0]*x + c[1])*x*x + c[2];
else if (x < 2) return ((c[3]*x + c[4])*x + c[5])*x + c[6];
else return 0;
}
double _coeffs[7];
};
class PtexGaussianFilter : public PtexWidth4Filter
{
public:
PtexGaussianFilter(PtexTexture* tx, const PtexFilter::Options& opts)
: PtexWidth4Filter(tx, opts, kernelFn) {}
private:
static double kernelFn(double x, const double*)
{
return exp(-2*x*x);
}
};
class PtexBoxFilter : public PtexSeparableFilter
{
public:
PtexBoxFilter(PtexTexture* tx, const PtexFilter::Options& opts)
: PtexSeparableFilter(tx, opts) {}
protected:
virtual void buildKernel(PtexSeparableKernel& k, float u, float v, float uw, float vw,
Res faceRes)
{
uw = PtexUtils::min(uw, 1.0f);
vw = PtexUtils::min(vw, 1.0f);
uw = PtexUtils::max(uw, 1.0f/(faceRes.u()));
vw = PtexUtils::max(vw, 1.0f/(faceRes.v()));
int ureslog2 = int(ceil(log2(1.0/uw))),
vreslog2 = int(ceil(log2(1.0/vw)));
Res res(ureslog2, vreslog2);
k.res = res;
u = u * k.res.u();
v = v * k.res.v();
uw *= k.res.u();
vw *= k.res.v();
double u1 = u - 0.5*uw, u2 = u + 0.5*uw;
double v1 = v - 0.5*vw, v2 = v + 0.5*vw;
double u1floor = floor(u1), u2ceil = ceil(u2);
double v1floor = floor(v1), v2ceil = ceil(v2);
k.u = int(u1floor);
k.v = int(v1floor);
k.uw = int(u2ceil)-k.u;
k.vw = int(v2ceil)-k.v;
computeWeights(k.ku, k.uw, 1-(u1-u1floor), 1-(u2ceil-u2));
computeWeights(k.kv, k.vw, 1-(v1-v1floor), 1-(v2ceil-v2));
}
private:
void computeWeights(double* kernel, int size, double f1, double f2)
{
assert(size >= 1 && size <= 3);
if (size == 1) {
kernel[0] = f1 + f2 - 1;
}
else {
kernel[0] = f1;
for (int i = 1; i < size-1; i++) kernel[i] = 1.0;
kernel[size-1] = f2;
}
}
};
class PtexBilinearFilter : public PtexSeparableFilter
{
public:
PtexBilinearFilter(PtexTexture* tx, const PtexFilter::Options& opts)
: PtexSeparableFilter(tx, opts) {}
protected:
virtual void buildKernel(PtexSeparableKernel& k, float u, float v, float uw, float vw,
Res faceRes)
{
uw = PtexUtils::min(uw, 1.0f);
vw = PtexUtils::min(vw, 1.0f);
uw = PtexUtils::max(uw, 1.0f/(faceRes.u()));
vw = PtexUtils::max(vw, 1.0f/(faceRes.v()));
const double roundWidth = 0.5849625007211563;
int ureslog2 = int(log2(1.0/uw) + roundWidth);
int vreslog2 = int(log2(1.0/vw) + roundWidth);
Res res(ureslog2, vreslog2);
k.res = res;
double upix = u * k.res.u() - 0.5;
double vpix = v * k.res.v() - 0.5;
float ufloor = floor(upix);
float vfloor = floor(vpix);
k.u = int(ufloor);
k.v = int(vfloor);
k.uw = 2;
k.vw = 2;
float ufrac = upix-ufloor, vfrac = vpix-vfloor;
k.ku[0] = 1 - ufrac;
k.ku[1] = ufrac;
k.kv[0] = 1 - vfrac;
k.kv[1] = vfrac;
}
};
}
PtexFilter* PtexFilter::getFilter(PtexTexture* tex, const PtexFilter::Options& opts)
{
switch (tex->meshType()) {
case Ptex::mt_quad:
switch (opts.filter) {
case -1: return new PtexMitchellFilter(tex, opts.sharpness);
case f_point: return new PtexPointFilter(tex);
case f_bilinear: return new PtexBilinearFilter(tex, opts);
default:
case f_box: return new PtexBoxFilter(tex, opts);
case f_gaussian: return new PtexGaussianFilter(tex, opts);
case f_bicubic: return new PtexBicubicFilter(tex, opts, opts.sharpness);
case f_bspline: return new PtexBicubicFilter(tex, opts, 0.0);
case f_catmullrom: return new PtexBicubicFilter(tex, opts, 1.0);
case f_mitchell: return new PtexBicubicFilter(tex, opts, 2.0/3.0);
}
break;
case Ptex::mt_triangle:
switch (opts.filter) {
case f_point: return new PtexPointFilterTri(tex);
default: return new PtexTriangleFilter(tex, opts);
}
break;
}
return 0;
}