diff --git a/uppsrc/Draw/Draw.upp b/uppsrc/Draw/Draw.upp index 2af7097e7..14bcf5a7c 100644 --- a/uppsrc/Draw/Draw.upp +++ b/uppsrc/Draw/Draw.upp @@ -51,6 +51,7 @@ file ImageChOp.cpp, ImageScale.cpp optimize_speed, BiCubic.cpp optimize_speed, + RescaleFilter.cpp, MakeCache.cpp, DrawRasterData.cpp, iml.h, diff --git a/uppsrc/Draw/ImageOp.h b/uppsrc/Draw/ImageOp.h index 22dfacdc9..3ec60d060 100644 --- a/uppsrc/Draw/ImageOp.h +++ b/uppsrc/Draw/ImageOp.h @@ -75,10 +75,6 @@ Image Rescale(const Image& src, Size sz, const Rect& src_rc, Gate2 pro Image Rescale(const Image& src, Size sz, Gate2 progress = false); Image Rescale(const Image& src, int cx, int cy, Gate2 progress = false); -Image RescaleBicubic(const Image& src, Size sz, const Rect& src_rc, Gate2 progress = false); -Image RescaleBicubic(const Image& img, int cx, int cy, Gate2 progress = false); -Image RescaleBicubic(const Image& img, Size sz, Gate2 progress = false); - struct ImageFilter9 { virtual RGBA operator()(const RGBA **mx) = 0; virtual ~ImageFilter9() {} @@ -164,3 +160,31 @@ Image CachedRescale(const Image& m, Size sz, const Rect& src); Image CachedRescale(const Image& m, Size sz); Image CachedRescalePaintOnly(const Image& m, Size sz, const Rect& src); Image CachedRescalePaintOnly(const Image& m, Size sz); + +Image RescaleBicubic(const Image& src, Size sz, const Rect& src_rc, Gate2 progress = false); +Image RescaleBicubic(const Image& img, Size sz, Gate2 progress = false); +Image RescaleBicubic(const Image& img, int cx, int cy, Gate2 progress = false); + +Image RescaleFilter(const Image& img, Size sz, const Rect& sr, + double (*kernel_fn)(double x), int kernel_width, Gate2 progress); +Image RescaleFilter(const Image& img, Size sz, + double (*kernel_fn)(double x), int kernel_width, Gate2 progress); +Image RescaleFilter(const Image& img, int cx, int cy, + double (*kernel_fn)(double x), int kernel_width, Gate2 progress); + +enum { + FILTER_NEAREST = 0, + FILTER_BILINEAR = 1, + FILTER_BSPLINE = 2, + FILTER_COSTELLO = 3, + FILTER_BICUBIC_MITCHELL = 4, + FILTER_BICUBIC_CATMULLROM = 5, + FILTER_LANCZOS2 = 6, + FILTER_LANCZOS3 = 7, + FILTER_LANCZOS4 = 8, + FILTER_LANCZOS5 = 9, +}; + +Image RescaleFilter(const Image& img, Size sz, const Rect& sr, int filter, Gate2 progress = false); +Image RescaleFilter(const Image& img, Size sz, int filter, Gate2 progress = false); +Image RescaleFilter(const Image& img, int cx, int cy, int filter, Gate2 progress = false); diff --git a/uppsrc/Draw/RescaleFilter.cpp b/uppsrc/Draw/RescaleFilter.cpp new file mode 100644 index 000000000..b82986a9b --- /dev/null +++ b/uppsrc/Draw/RescaleFilter.cpp @@ -0,0 +1,241 @@ +#include "Draw.h" + +NAMESPACE_UPP + +#define LDUMP(x) // DUMP(x) + +static const int *sGetKernel(double (*kfn)(double x), int a, int shift) +{ + INTERLOCKED { + static VectorMap, Buffer > kache; + Tuple3 key = MakeTuple((uintptr_t)kfn, a, shift); + Buffer *k = kache.FindPtr(key); + if(k) + return *k; + RTIMING("GenKernel"); + Buffer& ktab = kache.GetAdd(key); + ktab.Alloc(((2 * a) << shift) + 1); + for(int i = 0; i < ((2 * a) << shift) + 1; i++) + ktab[i] = int((1 << shift) * (*kfn)((double)i / (1 << shift) - a)); + return ktab; + } +} + +force_inline +int sGetk(const int *kernel, int x, int a, int shift) +{ + x += a << shift; + ASSERT(x >= 0 && x < ((2 * a) << shift) + 1); + return kernel[x]; +} + +static int sGeta(int a, int src, int tgt, int& shift) +{ + int n = min(max(src / tgt, 1) * a, 31); + shift = 8 - n / 8; + return n; +} + +Image RescaleFilter(const Image& img, Size sz, const Rect& sr, + double (*kfn)(double x), int a, + Gate2 progress) +{ + ASSERT(Rect(img.GetSize()).Contains(sr)); + Size isz = sr.GetSize(); + + int shiftx, shifty; + int ax = sGeta(a, isz.cx, sz.cx, shiftx); + int ay = sGeta(a, isz.cy, sz.cy, shifty); + + int shift = min(shiftx, shifty); + + const int *kernel = sGetKernel(kfn, a, shift); + + Buffer px(sz.cx * 2 * ax * 2 * ay * 2); + int *xd = px; + for(int x = 0; x < sz.cx; x++) { + int sx = x * isz.cx / sz.cx; + int dx = ((x * isz.cx) << shift) / sz.cx - (sx << shift); + for(int yy = -ay + 1; yy <= ay; yy++) { + for(int xx = -ax + 1; xx <= ax; xx++) { + *xd++ = minmax(sx + xx, 0, isz.cx - 1) + sr.left; + *xd++ = sGetk(kernel, ((xx << shift) - dx) * a / ax, a, shift); + } + } + } + + ImageBuffer ib(sz); + RGBA *t = ~ib; + for(int y = 0; y < sz.cy; y++) { + if(progress(y, sz.cy)) + break; + int sy = y * isz.cy / sz.cy; + int dy = ((y * isz.cy) << shift) / sz.cy - (sy << shift); + xd = ~px; + Buffer py(2 * ay * 2); + int *yd = py; + for(int yy = -ay + 1; yy <= ay; yy++) { + *yd++ = sGetk(kernel, ((yy << shift) - dy) * a / ay, a, shift); + *yd++ = minmax(sy + yy, 0, isz.cy - 1) + sr.top; + } + for(int x = 0; x < sz.cx; x++) { + int red = 0; + int green = 0; + int blue = 0; + int alpha = 0; + int w = 0; + yd = py; + int hasalpha = 0; + for(int yy = 2 * ay; yy-- > 0;) { + int ky = *yd++; + const RGBA *l = img[*yd++]; + for(int xx = 2 * ax; xx-- > 0;) { + const RGBA& s = l[*xd++]; + int weight = ky * *xd++; + red += weight * s.r; + green += weight * s.g; + blue += weight * s.b; + alpha += weight * s.a; + hasalpha |= s.a - 255; + w += weight; + } + } + if(hasalpha) { + t->a = alpha = Saturate255(alpha / w); + t->r = clamp(red / w, 0, alpha); + t->g = clamp(green / w, 0, alpha); + t->b = clamp(blue / w, 0, alpha); + } + else { + t->a = 255; + t->r = Saturate255(red / w); + t->g = Saturate255(green / w); + t->b = Saturate255(blue / w); + } + t++; + } + } + return ib; +} + +Image RescaleFilter(const Image& img, Size sz, + double (*kfn)(double x), int a, + Gate2 progress) +{ + return RescaleFilter(img, sz, img.GetSize(), kfn, a, progress); +} + +Image RescaleFilter(const Image& img, int cx, int cy, + double (*kfn)(double x), int a, + Gate2 progress) +{ + return RescaleFilter(img, Size(cx, cy), img.GetSize(), kfn, a, progress); +} + +static double sNearest(double x) +{ + return x >= -0.5 && x <= 0.5; +} + + +static double sBiCubic_(double x, double B, double C) +{ + x = fabs(x); + double x2 = x * x; + double x3 = x * x * x; + return + 1 / 6.0 * (x < 1 ? (12 - 9*B - 6*C) * x3 + (-18 + 12*B + 6*C) * x2 + (6 - 2*B) : + x < 2 ? (-B - 6*C) * x3 + (6*B + 30*C) * x2 + (-12*B - 48*C) *x + (8*B + 24*C) : + 0); +} + +static double sBspline(double x) +{ + return sBiCubic_(x, 1, 0); +} + +static double sMitchell(double x) +{ + return sBiCubic_(x, 1 / 3.0, 1 / 3.0); +} + +static double sCatmullRom(double x) +{ + return sBiCubic_(x, 0, 1 / 2); +} + +static double sLinear(double x) +{ + x = fabs(x); + if(x > 1) + return 0; + return 1 - fabs(x); +} + +static double sLanczos(double x, int a) +{ + x = fabs(x); + if(x < 1e-200) + return 1; + if(x >= a) + return 0; + return a * sin(M_PI * x) * sin(M_PI * x / a) / (M_PI * M_PI * x * x); +} + +static double sLanczos2(double x) +{ + return sLanczos(x, 2); +} + +static double sLanczos3(double x) +{ + return sLanczos(x, 3); +} + +static double sLanczos4(double x) +{ + return sLanczos(x, 4); +} + +static double sLanczos5(double x) +{ + return sLanczos(x, 5); +} + +static double sCostello(double x) +{ + x = fabs(x); + return x < 0.5 ? 0.75 - x * x : + x < 1.5 ? 0.5 * (x - 1.5) * (x - 1.5) : + 0; +} + +Image RescaleFilter(const Image& img, Size sz, const Rect& sr, int filter, Gate2 progress) +{ + static Tuple2 tab[] = { + sNearest, 1, + sLinear, 1, + sBspline, 2, + sCostello, 2, + sMitchell, 2, + sCatmullRom, 2, + sLanczos2, 2, + sLanczos3, 3, + sLanczos4, 4, + sLanczos5, 5, + }; + ASSERT(filter >= FILTER_NEAREST && filter <= FILTER_LANCZOS5); + return RescaleFilter(img, sz, sr, tab[filter].a, tab[filter].b, progress); +} + +Image RescaleFilter(const Image& img, Size sz, int filter, Gate2 progress) +{ + return RescaleFilter(img, sz, img.GetSize(), filter, progress); +} + +Image RescaleFilter(const Image& img, int cx, int cy, int filter, Gate2 progress) +{ + return RescaleFilter(img, Size(cx, cy), filter, progress); +} + +END_UPP_NAMESPACE