diff --git a/OverLua/docs/overlua.txt b/OverLua/docs/overlua.txt index ed86bfd3f..db093eef6 100644 --- a/OverLua/docs/overlua.txt +++ b/OverLua/docs/overlua.txt @@ -151,6 +151,12 @@ box filters over more general filters and using box blur over gaussian blur is probably no faster and might even be slower. +raster.directional_blur(surface, angle, sigma) + +Apply a variable strength directional gaussian kernel blur to the image. +Also known as motion blur. The angle is given in radians. + + raster.invert(surface) Invert the colour in the given surface. diff --git a/OverLua/docs/test3.lua b/OverLua/docs/test3.lua index f0197487a..8dc450f82 100644 --- a/OverLua/docs/test3.lua +++ b/OverLua/docs/test3.lua @@ -2,6 +2,7 @@ function render_frame(f, t) local surf = f.create_cairo_surface() --raster.gaussian_blur(surf, t)--1+(1-math.cos(t*10))*2) --raster.invert(surf) - raster.separable_filter(surf, {-1, 3, -1}, 1) + --raster.separable_filter(surf, {-1, 3, -1}, 1) + raster.directional_blur(surf, t, t/10) f.overlay_cairo_surface(surf, 0, 0) end diff --git a/OverLua/image.h b/OverLua/image.h index a42a16e55..6bedb1110 100644 --- a/OverLua/image.h +++ b/OverLua/image.h @@ -34,6 +34,7 @@ #include #include #include +#include // Forward @@ -49,6 +50,7 @@ namespace PixelFormat { void operator = (const T &n) { } }; typedef NopAssigningConstant ROA; // "read only alpha" + typedef NopAssigningConstant ROC; // "read only colour" // 24 bit formats struct RGB { @@ -146,6 +148,17 @@ namespace PixelFormat { template ABGR(const PixFmt &src) { a = src.A(); r = src.R(); g = src.G(); b = src.B(); } }; + // Alpha only + struct A8 { + uint8_t a; + inline ROC R() const { return ROC(); } + inline ROC G() const { return ROC(); } + inline ROC B() const { return ROC(); } + inline uint8_t &A() { return a; } inline uint8_t A() const { return a; } + A8() : a(0) { } + template A8(const PixFmt &src) { a = src.A(); } + }; + // cairo types // These are only true for little endian architectures // If OverLua is ever to run on big endian archs some conditional compiling should be used here @@ -573,47 +586,70 @@ typedef BaseImageAggregateImpl ImageABGR; // Access pixels with various edge conditions +namespace EdgeCondition { + template + inline PixFmt &blackness(BaseImage &img, int x, int y) + { + if (x < 0 || y < 0 || x >= img.width || x >= img.height) { + return PixFmt(); // all construct with all zeroes + } else { + return img.Pixel(x,y); + } + } -template -inline PixFmt &GetPixelEdgeBlackness(BaseImage &img, int x, int y) -{ - if (x < 0 || y < 0 || x >= img.width || x >= img.height) { - return PixFmt(); // all construct with all zeroes - } else { + template + inline PixFmt &closest(BaseImage &img, int x, int y) + { + if (x < 0) x = 0; + if (y < 0) y = 0; + if (x >= img.width) x = img.width-1; + if (y >= img.height) y = img.height - 1; + return img.Pixel(x,y); + } + + template + inline PixFmt &repeat(BaseImage &img, int x, int y) + { + while (x < 0) x += img.width; + while (y < 0) y += img.height; + while (x >= img.width) x -= img.width; + while (y >= img.height) y -= img.height; + return img.Pixel(x,y); + } + + template + inline PixFmt &mirror(BaseImage &img, int x, int y) + { + while (x < 0) x += img.width*2; + while (y < 0) y += img.height*2; + while (x >= img.width*2) x -= img.width*2; + while (y >= img.height*2) y -= img.height*2; + if (x >= img.width) x = img.width - x; + if (y >= img.height) y = img.height - y; return img.Pixel(x,y); } } -template -inline PixFmt &GetPixelEdgeClosest(BaseImage &img, int x, int y) -{ - if (x < 0) x = 0; - if (y < 0) y = 0; - if (x >= img.width) x = img.width-1; - if (y >= img.height) y = img.height - 1; - return img.Pixel(x,y); -} -template -inline PixFmt &GetPixelEdgeRepeat(BaseImage &img, int x, int y) +// FIXME: this is completely broken, the compiler won't accept it +// when instantiated with one of the EdgeCondition functions for EdgeCond +template +inline PixFmt GetPixelBilinear(BaseImage &img, double x, double y) { - while (x < 0) x += img.width; - while (y < 0) y += img.height; - while (x >= img.width) x -= img.width; - while (y >= img.height) y -= img.height; - return img.Pixel(x,y); -} + PixFmt res; + double xpct = x - floor(x), ypct = y - floor(y); -template -inline PixFmt &GetPixelEdgeMirror(BaseImage &img, int x, int y) -{ - while (x < 0) x += img.width*2; - while (y < 0) y += img.height*2; - while (x >= img.width*2) x -= img.width*2; - while (y >= img.height*2) y -= img.height*2; - if (x >= img.width) x = img.width - x; - if (y >= img.height) y = img.height - y; - return img.Pixel(x,y); + const PixFmt &src11 = EdgeCond(img, (int)x, (int)y); + const PixFmt &src12 = EdgeCond(img, (int)x, 1+(int)y); + const PixFmt &src21 = EdgeCond(img, 1+(int)x, (int)y); + const PixFmt &src22 = EdgeCond(img, 1+(int)x, 1+(int)y); + + res.R() += (1-xpct) * (1-ypct) * src11.R() + (1-xpct) * ypct * src12.R() + xpct * (1-ypct) * src21.R() + xpct * ypct * src22.R(); + res.G() += (1-xpct) * (1-ypct) * src11.G() + (1-xpct) * ypct * src12.G() + xpct * (1-ypct) * src21.G() + xpct * ypct * src22.G(); + res.B() += (1-xpct) * (1-ypct) * src11.B() + (1-xpct) * ypct * src12.B() + xpct * (1-ypct) * src21.B() + xpct * ypct * src22.B(); + res.A() += (1-xpct) * (1-ypct) * src11.A() + (1-xpct) * ypct * src12.A() + xpct * (1-ypct) * src21.A() + xpct * ypct * src22.A(); + + return res; } diff --git a/OverLua/raster_ops.cpp b/OverLua/raster_ops.cpp index 4473255de..c2e883205 100644 --- a/OverLua/raster_ops.cpp +++ b/OverLua/raster_ops.cpp @@ -256,6 +256,7 @@ void SeparableFilterY(unsigned char *src, unsigned char *dst, int width, int hei } +// Apply a simple separable FIR filter to an image void ApplySeparableFilter(lua_State *L, cairo_surface_t *surf, int *kernel, int kernel_size, int divisor) { // Get surface properties @@ -297,6 +298,89 @@ void ApplySeparableFilter(lua_State *L, cairo_surface_t *surf, int *kernel, int } +// Apply a general filter an image +template +void ApplyGeneralFilter(lua_State *L, cairo_surface_t *surf, FilterType &filter) +{ + // Get surface properties + cairo_surface_flush(surf); + int width = cairo_image_surface_get_width(surf); + int height = cairo_image_surface_get_height(surf); + ptrdiff_t stride = (ptrdiff_t)cairo_image_surface_get_stride(surf); + cairo_format_t format = cairo_image_surface_get_format(surf); + unsigned char *data = cairo_image_surface_get_data(surf); + + if (format != CAIRO_FORMAT_ARGB32 && format != CAIRO_FORMAT_RGB24 && format != CAIRO_FORMAT_A8) { + lua_pushliteral(L, "Unsupported image format for raster operation"); + lua_error(L); + } + + // Source image copy + unsigned char *wimg = new unsigned char[height*stride]; + memcpy(wimg, data, height*stride); + + if (format == CAIRO_FORMAT_ARGB32) { + BaseImage src(width, height, stride, wimg); + BaseImage dst(width, height, stride, data); +#pragma omp parallel for + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + dst.Pixel(x,y) = filter.argb32(src, x, y); + } + } + + } else if (format == CAIRO_FORMAT_RGB24) { + BaseImage src(width, height, stride, wimg); + BaseImage dst(width, height, stride, data); +#pragma omp parallel for + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + dst.Pixel(x,y) = filter.rgb24(src, x, y); + } + } + + } else if (format == CAIRO_FORMAT_A8) { + BaseImage src(width, height, stride, wimg); + BaseImage dst(width, height, stride, data); +#pragma omp parallel for + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + dst.Pixel(x,y) = filter.a8(src, x, y); + } + } + } + + // Clean up + cairo_surface_mark_dirty(surf); + delete[] wimg; +} + + +struct GaussianKernel { + int *kernel; + int width; + int divisor; + GaussianKernel(double sigma) + { + width = (int)(sigma*3 + 0.5) | 1; // binary-or with 1 to make sure the number is odd + if (width < 3) width = 3; + kernel = new int[width]; + kernel[width/2] = (int)(NormalDist(sigma, 0) * 255); + divisor = kernel[width/2]; + for (int x = width/2-1; x >= 0; x--) { + int val = (int)(NormalDist(sigma, width/2-x) * 255 + 0.5); + divisor += val*2; + kernel[x] = val; + kernel[width - x - 1] = val; + } + } + ~GaussianKernel() + { + delete[] kernel; + } +}; + + // raster.gaussian_blur(imgsurf, sigma) static int gaussian_blur(lua_State *L) { @@ -305,21 +389,9 @@ static int gaussian_blur(lua_State *L) double sigma = luaL_checknumber(L, 2); // Generate gaussian kernel - int kernel_size = (int)(sigma*3 + 0.5) | 1; // binary-or with 1 to make sure the number is odd - if (kernel_size < 3) kernel_size = 3; - int *kernel = new int[kernel_size]; - kernel[kernel_size/2] = (int)(NormalDist(sigma, 0) * 255); - int ksum = kernel[kernel_size/2]; - for (int x = kernel_size/2-1; x >= 0; x--) { - int val = (int)(NormalDist(sigma, kernel_size/2-x) * 255 + 0.5); - ksum += val*2; - kernel[x] = val; - kernel[kernel_size - x - 1] = val; - } + GaussianKernel gk(sigma); - ApplySeparableFilter(L, surf, kernel, kernel_size, ksum); - - delete[] kernel; + ApplySeparableFilter(L, surf, gk.kernel, gk.width, gk.divisor); return 0; } @@ -342,6 +414,85 @@ static int box_blur(lua_State *L) } +// TODO: figure out how to make this use bilinear pixel grabbing instead +struct DirectionalBlurFilter { + GaussianKernel gk; + double xstep, ystep; + DirectionalBlurFilter(double angle, double sigma) : + gk(sigma) + { + xstep = cos(angle); + ystep = -sin(angle); + } + inline PixelFormat::A8 a8(BaseImage &src, int x, int y) + { + int a = 0; + for (int t = -gk.width/2, i = 0; i < gk.width; t++, i++) { + //PixelFormat::A8 &srcpx = GetPixelBilinear(src, x+xstep*t, y+ystep*t); + PixelFormat::A8 &srcpx = EdgeCondition::repeat(src, (int)(x+xstep*t), (int)(y+ystep*t)); + a += srcpx.A() * gk.kernel[i]; + } + PixelFormat::A8 res; + a = a / gk.divisor; if (a < 0) a = 0; if (a > 255) a = 255; + res.A() = a; + return res; + } + inline PixelFormat::cairo_rgb24 rgb24(BaseImage &src, int x, int y) + { + int r = 0, g = 0, b = 0; + for (int t = -gk.width/2, i = 0; i < gk.width; t++, i++) { + //PixelFormat::cairo_rgb24 &srcpx = GetPixelBilinear(src, x+xstep*t, y+ystep*t); + PixelFormat::cairo_rgb24 &srcpx = EdgeCondition::repeat(src, (int)(x+xstep*t), (int)(y+ystep*t)); + r += srcpx.R() * gk.kernel[i]; + g += srcpx.G() * gk.kernel[i]; + b += srcpx.B() * gk.kernel[i]; + } + PixelFormat::cairo_rgb24 res; + r = r / gk.divisor; if (r < 0) r = 0; if (r > 255) r = 255; + g = g / gk.divisor; if (g < 0) g = 0; if (g > 255) g = 255; + b = b / gk.divisor; if (b < 0) b = 0; if (b > 255) b = 255; + res.R() = r; + res.G() = g; + res.B() = b; + return res; + } + inline PixelFormat::cairo_argb32 argb32(BaseImage &src, int x, int y) + { + int a = 0, r = 0, g = 0, b = 0; + for (int t = -gk.width/2, i = 0; i < gk.width; t++, i++) { + //PixelFormat::cairo_argb32 &srcpx = GetPixelBilinear(src, x+xstep*t, y+ystep*t); + PixelFormat::cairo_argb32 &srcpx = EdgeCondition::repeat(src, (int)(x+xstep*t), (int)(y+ystep*t)); + a += srcpx.A() * gk.kernel[i]; + r += srcpx.R() * gk.kernel[i]; + g += srcpx.G() * gk.kernel[i]; + b += srcpx.B() * gk.kernel[i]; + } + PixelFormat::cairo_argb32 res; + a = a / gk.divisor; if (a < 0) a = 0; if (a > 255) a = 255; + r = r / gk.divisor; if (r < 0) r = 0; if (r > 255) r = 255; + g = g / gk.divisor; if (g < 0) g = 0; if (g > 255) g = 255; + b = b / gk.divisor; if (b < 0) b = 0; if (b > 255) b = 255; + res.A() = a; + res.R() = r; + res.G() = g; + res.B() = b; + return res; + }}; + + +static int directional_blur(lua_State *L) +{ + cairo_surface_t *surf = CheckSurface(L, 1); + double angle = luaL_checknumber(L, 2); + double sigma = luaL_checknumber(L, 3); + + DirectionalBlurFilter filter(angle, sigma); + ApplyGeneralFilter(L, surf, filter); + + return 0; +} + + static int invert_image(lua_State *L) { cairo_surface_t *surf = CheckSurface(L, 1); @@ -444,7 +595,7 @@ static int separable_filter(lua_State *L) // Registration static luaL_Reg rasterlib[] = { - {"gaussian_blur", gaussian_blur}, {"box_blur", box_blur}, + {"gaussian_blur", gaussian_blur}, {"box_blur", box_blur}, {"directional_blur", directional_blur}, {"separable_filter", separable_filter}, {"invert", invert_image}, {NULL, NULL}