/* * OverLua RGB(A) image interface * Copyright 2007 Niels Martin Hansen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Contact: E-mail: IRC: jfs in #aegisub on irc.rizon.net */ #ifndef VIDEO_FRAME_H #define VIDEO_FRAME_H #include "../lua51/src/lua.h" #include "../lua51/src/lauxlib.h" #include "cairo_wrap.h" #include #include #include #include #include #include // Forward class BaseImageAggregate; // Supported pixel formats namespace PixelFormat { // A constant value with a fake assignment operator, taking up no space template struct NopAssigningConstant { operator T() const { return v; } void operator = (const T &n) { } }; typedef NopAssigningConstant ROA; // "read only alpha" typedef NopAssigningConstant ROC; // "read only colour" // 24 bit formats struct RGB { uint8_t r, g, b; inline uint8_t &R() { return r; } inline uint8_t R() const { return r; } inline uint8_t &G() { return g; } inline uint8_t G() const { return g; } inline uint8_t &B() { return b; } inline uint8_t B() const { return b; } inline ROA A() const { return ROA(); } RGB() : r(0), g(0), b(0) { } template RGB(const PixFmt &src) { r = src.R(); g = src.G(); b = src.B(); } }; struct BGR { uint8_t b, g, r; inline uint8_t &R() { return r; } inline uint8_t R() const { return r; } inline uint8_t &G() { return g; } inline uint8_t G() const { return g; } inline uint8_t &B() { return b; } inline uint8_t B() const { return b; } inline ROA A() const { return ROA(); } BGR() : r(0), g(0), b(0) { } template BGR(const PixFmt &src) { r = src.R(); g = src.G(); b = src.B(); } }; // 32 bit alpha-less formats struct RGBX { uint8_t r, g, b, x; inline uint8_t &R() { return r; } inline uint8_t R() const { return r; } inline uint8_t &G() { return g; } inline uint8_t G() const { return g; } inline uint8_t &B() { return b; } inline uint8_t B() const { return b; } inline ROA A() const { return ROA(); } RGBX() : r(0), g(0), b(0) { } template RGBX(const PixFmt &src) { r = src.R(); g = src.G(); b = src.B(); } }; struct BGRX { uint8_t b, g, r, x; inline uint8_t &R() { return r; } inline uint8_t R() const { return r; } inline uint8_t &G() { return g; } inline uint8_t G() const { return g; } inline uint8_t &B() { return b; } inline uint8_t B() const { return b; } inline ROA A() const { return ROA(); } BGRX() : r(0), g(0), b(0) { } template BGRX(const PixFmt &src) { r = src.R(); g = src.G(); b = src.B(); } }; struct XRGB { uint8_t x, r, g, b; inline uint8_t &R() { return r; } inline uint8_t R() const { return r; } inline uint8_t &G() { return g; } inline uint8_t G() const { return g; } inline uint8_t &B() { return b; } inline uint8_t B() const { return b; } inline ROA A() const { return ROA(); } XRGB() : r(0), g(0), b(0) { } template XRGB(const PixFmt &src) { r = src.R(); g = src.G(); b = src.B(); } }; struct XBGR { uint8_t x, b, g, r; inline uint8_t &R() { return r; } inline uint8_t R() const { return r; } inline uint8_t &G() { return g; } inline uint8_t G() const { return g; } inline uint8_t &B() { return b; } inline uint8_t B() const { return b; } inline ROA A() const { return ROA(); } XBGR() : r(0), g(0), b(0) { } template XBGR(const PixFmt &src) { r = src.R(); g = src.G(); b = src.B(); } }; // 32 bit with alpha struct RGBA { uint8_t r, g, b, a; inline uint8_t &R() { return r; } inline uint8_t R() const { return r; } inline uint8_t &G() { return g; } inline uint8_t G() const { return g; } inline uint8_t &B() { return b; } inline uint8_t B() const { return b; } inline uint8_t &A() { return a; } inline uint8_t A() const { return a; } RGBA() : r(0), g(0), b(0), a(0) { } template RGBA(const PixFmt &src) { a = src.a; r = src.r; g = src.g; b = src.b; } }; struct BGRA { uint8_t b, g, r, a; inline uint8_t &R() { return r; } inline uint8_t R() const { return r; } inline uint8_t &G() { return g; } inline uint8_t G() const { return g; } inline uint8_t &B() { return b; } inline uint8_t B() const { return b; } inline uint8_t &A() { return a; } inline uint8_t A() const { return a; } BGRA() : r(0), g(0), b(0), a(0) { } template BGRA(const PixFmt &src) { a = src.A(); r = src.R(); g = src.G(); b = src.B(); } }; struct ARGB { uint8_t a, r, g, b; inline uint8_t &R() { return r; } inline uint8_t R() const { return r; } inline uint8_t &G() { return g; } inline uint8_t G() const { return g; } inline uint8_t &B() { return b; } inline uint8_t B() const { return b; } inline uint8_t &A() { return a; } inline uint8_t A() const { return a; } ARGB() : r(0), g(0), b(0), a(0) { } template ARGB(const PixFmt &src) { a = src.A(); r = src.R(); g = src.G(); b = src.B(); } }; struct ABGR { uint8_t a, b, g, r; inline uint8_t &R() { return r; } inline uint8_t R() const { return r; } inline uint8_t &G() { return g; } inline uint8_t G() const { return g; } inline uint8_t &B() { return b; } inline uint8_t B() const { return b; } inline uint8_t &A() { return a; } inline uint8_t A() const { return a; } ABGR() : r(0), g(0), b(0), a(0) { } 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 typedef BGRX cairo_rgb24; typedef BGRA cairo_argb32; }; // Support any interleaved RGB format with 8 bit per channel // You usually don't want to instance this class directly, // look at OverLuaFrameAggregate defined below template class BaseImage { public: typedef BaseImage MyType; // video properties int width; int height; ptrdiff_t stride; unsigned char *data; // Access a pixel value inline const PixFmt &Pixel(int x, int y) const { return *((PixFmt*)(data + y*stride) + x) } inline PixFmt &Pixel(int x, int y) { return *((PixFmt*)(data + y*stride) + x); } BaseImage(unsigned _width, unsigned _height, ptrdiff_t _stride, unsigned char *_data) : width(_width), height(_height), stride(_stride), data(_data) { owndata = false; // nothing further to init } BaseImage(const MyType &src, bool copydata = false) { width = src.width; height = src.height; stride = src.stride; owndata = copydata; if (copydata) { data = new unsigned char[height*stride]; memcpy(data, src.data, height*stride); } else { data = src.data; } } ~BaseImage() { if (owndata) delete[] data; } // should never be called more than once on the same C++ object void CreateLuaObject(lua_State *L, BaseImageAggregate *aggregate = 0) { // create userdata object MyType **ud = (MyType**)lua_newuserdata(L, sizeof(MyType*)); *ud = this; // create metatable lua_newtable(L); lua_pushcclosure(L, lua_indexget, 0); lua_setfield(L, -2, "__index"); lua_pushcclosure(L, lua_indexset, 0); lua_setfield(L, -2, "__newindex"); lua_pushcclosure(L, lua_getpixel, 0); lua_setfield(L, -2, "__call"); lua_pushcclosure(L, lua_finalize, 0); lua_setfield(L, -2, "__gc"); if (aggregate) { lua_pushlightuserdata(L, aggregate); lua_setfield(L, -2, "image"); } lua_setmetatable(L, -2); } private: bool owndata; // set a pixel colour static int lua_indexset(lua_State *L) { // first arg = object // second arg = index // third arg = value MyType **ud = (MyType**)lua_touserdata(L, 1); if (lua_isnumber(L, 2)) { if (lua_istable(L, 3)) { int n = (int)lua_tointeger(L, 2); int x = n % (*ud)->width; int y = n / (*ud)->width; if (x < 0 || y < 0 || x >= (*ud)->width || y >= (*ud)->height) return 0; // read first three entries from table PixFmt color; lua_pushnil(L); if (!lua_next(L, 3)) goto badtable; if (!lua_isnumber(L, -1)) goto badtable; color.r = (unsigned char)lua_tointeger(L, -1); lua_pop(L, 1); if (!lua_next(L, 3)) goto badtable; if (!lua_isnumber(L, -1)) goto badtable; color.g = (unsigned char)lua_tointeger(L, -1); lua_pop(L, 1); if (!lua_next(L, 3)) goto badtable; if (!lua_isnumber(L, -1)) goto badtable; color.b = (unsigned char)lua_tointeger(L, -1); lua_pop(L, 2); (*ud)->Pixel(x, y) = color; return 0; } else { badtable: lua_pushliteral(L, "Value set into image pixel must be a table with 3 entries"); lua_error(L); return 0; } } lua_pushliteral(L, "Undefined field in image"); lua_error(L); return 0; } // get a pixel colour or some other property static int lua_indexget(lua_State *L) { // first arg = object // second arg = index MyType **ud = (MyType**)lua_touserdata(L, 1); if (lua_type(L, 2) == LUA_TSTRING) { const char *fieldname = lua_tostring(L, 2); if (strcmp(fieldname, "width") == 0) { lua_pushnumber(L, (*ud)->width); } else if (strcmp(fieldname, "height") == 0) { lua_pushnumber(L, (*ud)->height); } else if (strcmp(fieldname, "copy") == 0) { lua_pushvalue(L, 1); lua_pushcclosure(L, lua_copyfunc, 1); } else if (strcmp(fieldname, "create_cairo_surface") == 0) { lua_pushvalue(L, 1); lua_pushcclosure(L, lua_create_cairo_surface, 1); } else if (strcmp(fieldname, "overlay_cairo_surface") == 0) { lua_pushvalue(L, 1); lua_pushcclosure(L, lua_overlay_cairo_surface, 1); } else { lua_pushfstring(L, "Undefined field name in image: %s", fieldname); lua_error(L); } return 1; } lua_pushfstring(L, "Unhandled field type indexing image: %s", lua_typename(L, lua_type(L, 2))); lua_error(L); return 0; } static int lua_getpixel(lua_State *L) { // first arg = object // second arg = x // third arg = y MyType **ud = (MyType**)lua_touserdata(L, 1); int x = luaL_checkint(L, 2); int y = luaL_checkint(L, 3); if (x < 0 || y < 0 || x >= (*ud)->width || y >= (*ud)->height) { lua_pushinteger(L, 0); lua_pushinteger(L, 0); lua_pushinteger(L, 0); } else { const PixFmt &p = (*ud)->Pixel(x, y); lua_pushinteger(L, p.r); lua_pushinteger(L, p.g); lua_pushinteger(L, p.b); } return 3; } static int lua_finalize(lua_State *L) { MyType **ud = (MyType**)lua_touserdata(L, 1); delete *ud; return 0; } static int lua_copyfunc(lua_State *L) { MyType **ud = (MyType**)lua_touserdata(L, lua_upvalueindex(1)); MyType *copy = new MyType(**ud, true); copy->CreateLuaObject(L); return 1; } static int lua_create_cairo_surface(lua_State *L) { MyType **ud = (MyType**)lua_touserdata(L, lua_upvalueindex(1)); // Create a new surface of same resolution // Always RGB24 format since we don't support video with alpha cairo_surface_t *surf = cairo_image_surface_create(CAIRO_FORMAT_RGB24, (*ud)->width, (*ud)->height); if (!surf || cairo_surface_status(surf) != CAIRO_STATUS_SUCCESS) { lua_pushliteral(L, "Unable to create cairo surface from image"); lua_error(L); return 0; } // Prepare copying pixels from frame to surface unsigned char *surfdata = cairo_image_surface_get_data(surf); int surfstride = cairo_image_surface_get_stride(surf); // Copy pixels int height = (*ud)->height; int width = (*ud)->width; #pragma omp parallel for for (int y = 0; y < height; y++) { PixFmt *ipix = (PixFmt*)((*ud)->data + y*((*ud)->stride)); PixelFormat::cairo_rgb24 *opix = (PixelFormat::cairo_rgb24*)(surfdata + y*surfstride); for (int x = 0; x < width; x++) { *opix++ = *ipix++; } } cairo_surface_mark_dirty(surf); // Create Lua object LuaCairoSurface *lsurf = new LuaCairoSurface(L, surf); // Release our reference to the surface cairo_surface_destroy(surf); // Return it to Lua return 1; } static int lua_overlay_cairo_surface(lua_State *L) { // Get inputs MyType **ud = (MyType**)lua_touserdata(L, lua_upvalueindex(1)); LuaCairoSurface *surfwrap = LuaCairoSurface::GetObjPointer(L, 1); if (!surfwrap) { lua_pushliteral(L, "Argument to overlay_cairo_surface is not a cairo surface"); lua_error(L); return 0; } int xpos = luaL_checkint(L, 2); int ypos = luaL_checkint(L, 3); // More argument checks cairo_surface_t *surf = surfwrap->GetSurface(); if (cairo_surface_get_type(surf) != CAIRO_SURFACE_TYPE_IMAGE) { lua_pushliteral(L, "Argument to overlay_cairo_surface is not a cairo image surface"); lua_error(L); return 0; } // Prepare some data int fwidth = (*ud)->width, fheight = (*ud)->height; int swidth = cairo_image_surface_get_width(surf), sheight = cairo_image_surface_get_height(surf); int sstride = cairo_image_surface_get_stride(surf); // Prepare and get read pointer for source image cairo_surface_flush(surf); unsigned char *sdata = cairo_image_surface_get_data(surf); // Check that the overlaid surface won't be entirely outside the frame if (xpos <= -swidth || ypos <= -sheight || xpos > fwidth || ypos > fheight) return 0; // Pick overlay algorithm cairo_format_t sformat = cairo_image_surface_get_format(surf); if (sformat == CAIRO_FORMAT_ARGB32) { // This has pre-multipled alpha int fy = ypos; // frame y if (fy < 0) fy = 0, sdata -= ypos, sheight -= ypos; int slines_to_compose = sheight, flines_to_compose = fheight; int lines_to_compose = (slines_to_composePixel(fx, fy+composition_line); unsigned char a = 0xff - sline[sx].a; pix.r = sline[sx].r + a*pix.r/255; pix.g = sline[sx].g + a*pix.g/255; pix.b = sline[sx].b + a*pix.b/255; } } } else if (sformat == CAIRO_FORMAT_RGB24) { // Assume opaque alpha for all pixels int fy = ypos; // frame y if (fy < 0) fy = 0, sdata -= ypos, sheight -= ypos; int slines_to_compose = sheight, flines_to_compose = fheight; int lines_to_compose = (slines_to_composePixel(fx, fy+composition_line) = sline[sx]; } } } else if (sformat == CAIRO_FORMAT_A8) { // This one is problematic - it doesn't contain any colour information // Two primary choices would be to fill with either black or white, // but neither would be an optimum solution. // A third option would be to take a fourth argument to this function, // specifying the colour to be used. lua_pushliteral(L, "8 bpp alpha images are not (yet) supported for overlay operations"); lua_error(L); return 0; } else if (sformat == CAIRO_FORMAT_A1) { lua_pushliteral(L, "1 bpp alpha images are not supported for overlay operations"); lua_error(L); return 0; } else { lua_pushliteral(L, "Unknown source image format for overlay operation"); lua_error(L); return 0; } return 0; } }; // Now something so we can pretend all images have the same pixel format // and can pass around pointers to objects of one fixed base class. class BaseImageAggregate { public: virtual PixelFormat::ARGB GetPixel(int x, int y) = 0; virtual void SetPixel(int x, int y, const PixelFormat::ARGB &val) = 0; virtual unsigned GetWidth() = 0; virtual unsigned GetHeight() = 0; virtual void CreateLuaObject(lua_State *L) = 0; }; template class BaseImageAggregateImpl : public BaseImageAggregate { public: typedef BaseImage ImageType; private: ImageType *frame; bool ownframe; public: BaseImageAggregateImpl(unsigned _width, unsigned _height, ptrdiff_t _stride, unsigned char *_data) { frame = new ImageType(_width, _height, _stride, _data); ownframe = true; } BaseImageAggregateImpl(ImageType *_frame) { frame = _frame; ownframe = false; } PixelFormat::ARGB GetPixel(int x, int y) { return PixelFormat::ARGB(frame->Pixel(x, y)); } void SetPixel(int x, int y, const PixelFormat::ARGB &val) { frame->Pixel(x, y) = PixFmt(val); } unsigned GetWidth() { return frame->width; } unsigned GetHeight() { return frame->height; } void CreateLuaObject(lua_State *L) { frame->CreateLuaObject(L, this); } }; // All common, sensible formats typedef BaseImageAggregateImpl ImageRGB; typedef BaseImageAggregateImpl ImageBGR; typedef BaseImageAggregateImpl ImageRGBX; typedef BaseImageAggregateImpl ImageBGRX; typedef BaseImageAggregateImpl ImageXRGB; typedef BaseImageAggregateImpl ImageXBGR; typedef BaseImageAggregateImpl ImageRGBA; typedef BaseImageAggregateImpl ImageBGRA; typedef BaseImageAggregateImpl ImageARGB; typedef BaseImageAggregateImpl ImageABGR; // Access pixels with various edge conditions namespace EdgeCondition { template struct Blackness { static inline PixFmt &get(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 struct Closest { static inline PixFmt &get(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 struct Repeat { static inline PixFmt &get(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 struct Mirror { static inline PixFmt &get(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*2 - x; if (y >= img.height) y = img.height*2 - y; return img.Pixel(x,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) { PixFmt res; double xpct = x - floor(x), ypct = y - floor(y); if (xpct == 0 && ypct == 0) return EdgeCond::get(img, (int)x, (int)y); const PixFmt &src11 = EdgeCond::get(img, (int)x, (int)y); const PixFmt &src12 = EdgeCond::get(img, (int)x, 1+(int)y); const PixFmt &src21 = EdgeCond::get(img, 1+(int)x, (int)y); const PixFmt &src22 = EdgeCond::get(img, 1+(int)x, 1+(int)y); res.R() = (uint8_t)((1-xpct) * (1-ypct) * src11.R() + (1-xpct) * ypct * src12.R() + xpct * (1-ypct) * src21.R() + xpct * ypct * src22.R()); res.G() = (uint8_t)((1-xpct) * (1-ypct) * src11.G() + (1-xpct) * ypct * src12.G() + xpct * (1-ypct) * src21.G() + xpct * ypct * src22.G()); res.B() = (uint8_t)((1-xpct) * (1-ypct) * src11.B() + (1-xpct) * ypct * src12.B() + xpct * (1-ypct) * src21.B() + xpct * ypct * src22.B()); res.A() = (uint8_t)((1-xpct) * (1-ypct) * src11.A() + (1-xpct) * ypct * src12.A() + xpct * (1-ypct) * src21.A() + xpct * ypct * src22.A()); return res; } #endif