Browse Source

Add support for rgba4, rgb5a1, rgb565, and rgb10a2 ImageData formats. Also fix normalized 8 and 16 bit per component ImageData to round instead of floor (post-converted fixed point) fractional values in setPixel and mapPixel.

Alex Szpakowski 6 years ago
parent
commit
94c66918a8

+ 21 - 1
src/modules/image/ImageData.cpp

@@ -463,7 +463,7 @@ void ImageData::paste(ImageData *src, int dx, int dy, int sx, int sy, int sw, in
 				pasteRGBA32FtoRGBA16F(rowsrc, rowdst, sw);
 
 			else
-				throw love::Exception("Unsupported pixel format combination in ImageData:paste!");
+				throw love::Exception("Unsupported pixel format combination in ImageData:paste.");
 		}
 	}
 }
@@ -494,12 +494,32 @@ bool ImageData::validPixelFormat(PixelFormat format)
 	case PIXELFORMAT_R32F:
 	case PIXELFORMAT_RG32F:
 	case PIXELFORMAT_RGBA32F:
+	case PIXELFORMAT_RGBA4:
+	case PIXELFORMAT_RGB5A1:
+	case PIXELFORMAT_RGB565:
+	case PIXELFORMAT_RGB10A2:
 		return true;
 	default:
 		return false;
 	}
 }
 
+bool ImageData::canPaste(PixelFormat src, PixelFormat dst)
+{
+	if (src == dst)
+		return true;
+
+	if (!(src == PIXELFORMAT_RGBA8 || src == PIXELFORMAT_RGBA16
+		|| src == PIXELFORMAT_RGBA16F || src == PIXELFORMAT_RGBA32F))
+		return false;
+
+	if (!(dst == PIXELFORMAT_RGBA8 || dst == PIXELFORMAT_RGBA16
+		|| dst == PIXELFORMAT_RGBA16F || dst == PIXELFORMAT_RGBA32F))
+		return false;
+
+	return true;
+}
+
 bool ImageData::getConstant(const char *in, FormatHandler::EncodedFormat &out)
 {
 	return encodedFormats.find(in, out);

+ 3 - 0
src/modules/image/ImageData.h

@@ -44,6 +44,8 @@ union Pixel
 	uint16 rgba16[4];
 	half   rgba16f[4];
 	float  rgba32f[4];
+	uint16 packed16;
+	uint32 packed32;
 };
 
 /**
@@ -114,6 +116,7 @@ public:
 	size_t getPixelSize() const;
 
 	static bool validPixelFormat(PixelFormat format);
+	static bool canPaste(PixelFormat src, PixelFormat dst);
 
 	static bool getConstant(const char *in, FormatHandler::EncodedFormat &out);
 	static bool getConstant(FormatHandler::EncodedFormat in, const char *&out);

+ 91 - 4
src/modules/image/wrap_ImageData.cpp

@@ -92,18 +92,18 @@ template <int components>
 static void luax_checkpixel_unorm8(lua_State *L, int startidx, Pixel &p)
 {
 	for (int i = 0; i < std::min(components, 3); i++)
-		p.rgba8[i] = (uint8) (luax_checknumberclamped01(L, startidx + i) * 255.0);
+		p.rgba8[i] = (uint8) ((luax_checknumberclamped01(L, startidx + i) * 255.0) + 0.5);
 	if (components > 3)
-		p.rgba8[3] = (uint8) (luax_optnumberclamped01(L, startidx + 3, 1.0) * 255.0);
+		p.rgba8[3] = (uint8) ((luax_optnumberclamped01(L, startidx + 3, 1.0) * 255.0) + 0.5);
 }
 
 template <int components>
 static void luax_checkpixel_unorm16(lua_State *L, int startidx, Pixel &p)
 {
 	for (int i = 0; i < std::min(components, 3); i++)
-		p.rgba16[i] = (uint16) (luax_checknumberclamped01(L, startidx + i) * 65535.0);
+		p.rgba16[i] = (uint16) ((luax_checknumberclamped01(L, startidx + i) * 65535.0) + 0.5);
 	if (components > 3)
-		p.rgba16[3] = (uint16) (luax_optnumberclamped01(L, startidx + 3, 1.0) * 65535.0);
+		p.rgba16[3] = (uint16) ((luax_optnumberclamped01(L, startidx + 3, 1.0) * 65535.0) + 0.5);
 }
 
 template <int components>
@@ -184,6 +184,45 @@ static void luax_checkpixel_rgba32f(lua_State *L, int startidx, Pixel &p)
 	luax_checkpixel_float32<4>(L, startidx, p);
 }
 
+static void luax_checkpixel_rgba4(lua_State *L, int startidx, Pixel &p)
+{
+	// LSB->MSB: [a, b, g, r]
+	uint16 r = ((uint16) ((luax_checknumberclamped01(L, startidx + 0) * 0xF) + 0.5)) << 12;
+	uint16 g = ((uint16) ((luax_checknumberclamped01(L, startidx + 1) * 0xF) + 0.5)) << 8;
+	uint16 b = ((uint16) ((luax_checknumberclamped01(L, startidx + 2) * 0xF) + 0.5)) << 4;
+	uint16 a = ((uint16) ((luax_optnumberclamped01(L, startidx + 3, 1.0) * 0xF) + 0.5)) << 0;
+	p.packed16 = r | g | b | a;
+}
+
+static void luax_checkpixel_rgb5a1(lua_State *L, int startidx, Pixel &p)
+{
+	// LSB->MSB: [a, b, g, r]
+	uint16 r = ((uint16) ((luax_checknumberclamped01(L, startidx + 0) * 0x1F) + 0.5)) << 11;
+	uint16 g = ((uint16) ((luax_checknumberclamped01(L, startidx + 1) * 0x1F) + 0.5)) << 6;
+	uint16 b = ((uint16) ((luax_checknumberclamped01(L, startidx + 2) * 0x1F) + 0.5)) << 1;
+	uint16 a = ((uint16) ((luax_optnumberclamped01(L, startidx + 3, 1.0) * 0x1) + 0.5)) << 0;
+	p.packed16 = r | g | b | a;
+}
+
+static void luax_checkpixel_rgb565(lua_State *L, int startidx, Pixel &p)
+{
+	// LSB->MSB: [b, g, r]
+	uint16 r = ((uint16) ((luax_checknumberclamped01(L, startidx + 0) * 0x1F) + 0.5)) << 11;
+	uint16 g = ((uint16) ((luax_checknumberclamped01(L, startidx + 1) * 0x3F) + 0.5)) << 5;
+	uint16 b = ((uint16) ((luax_checknumberclamped01(L, startidx + 2) * 0x1F) + 0.5)) << 0;
+	p.packed16 = r | g | b;
+}
+
+static void luax_checkpixel_rgb10a2(lua_State *L, int startidx, Pixel &p)
+{
+	// LSB->MSB: [r, g, b, a]
+	uint32 r = ((uint32) ((luax_checknumberclamped01(L, startidx + 0) * 0x3FF) + 0.5)) << 0;
+	uint32 g = ((uint32) ((luax_checknumberclamped01(L, startidx + 1) * 0x3FF) + 0.5)) << 10;
+	uint32 b = ((uint32) ((luax_checknumberclamped01(L, startidx + 2) * 0x3FF) + 0.5)) << 20;
+	uint32 a = ((uint32) ((luax_optnumberclamped01(L, startidx + 3, 1.0) * 0x3) + 0.5)) << 30;
+	p.packed32 = r | g | b | a;
+}
+
 static lua_Number fillValues[] = {0.0, 0.0, 0.0, 1.0};
 
 template <int components>
@@ -286,6 +325,46 @@ static int luax_pushpixel_rgba32f(lua_State *L, const Pixel &p)
 	return luax_pushpixel_float32<4>(L, p);
 }
 
+static int luax_pushpixel_rgba4(lua_State *L, const Pixel &p)
+{
+	// LSB->MSB: [a, b, g, r]
+	lua_pushnumber(L, ((p.packed16 >> 12) & 0xF) / (double)0xF);
+	lua_pushnumber(L, ((p.packed16 >>  8) & 0xF) / (double)0xF);
+	lua_pushnumber(L, ((p.packed16 >>  4) & 0xF) / (double)0xF);
+	lua_pushnumber(L, ((p.packed16 >>  0) & 0xF) / (double)0xF);
+	return 4;
+}
+
+static int luax_pushpixel_rgb5a1(lua_State *L, const Pixel &p)
+{
+	// LSB->MSB: [a, b, g, r]
+	lua_pushnumber(L, ((p.packed16 >> 11) & 0x1F) / (double)0x1F);
+	lua_pushnumber(L, ((p.packed16 >>  6) & 0x1F) / (double)0x1F);
+	lua_pushnumber(L, ((p.packed16 >>  1) & 0x1F) / (double)0x1F);
+	lua_pushnumber(L, ((p.packed16 >>  0) & 0x1)  / (double)0x1);
+	return 4;
+}
+
+static int luax_pushpixel_rgb565(lua_State *L, const Pixel &p)
+{
+	// LSB->MSB: [b, g, r]
+	lua_pushnumber(L, ((p.packed16 >> 11) & 0x1F) / (double)0x1F);
+	lua_pushnumber(L, ((p.packed16 >>  5) & 0x3F) / (double)0x3F);
+	lua_pushnumber(L, ((p.packed16 >>  0) & 0x1F) / (double)0x1F);
+	lua_pushnumber(L, 1.0);
+	return 4;
+}
+
+static int luax_pushpixel_rgb10a2(lua_State *L, const Pixel &p)
+{
+	// LSB->MSB: [r, g, b, a]
+	lua_pushnumber(L, ((p.packed16 >>  0) & 0x3FF) / (double)0x3FF);
+	lua_pushnumber(L, ((p.packed16 >> 10) & 0x3FF) / (double)0x3FF);
+	lua_pushnumber(L, ((p.packed16 >> 20) & 0x3FF) / (double)0x3FF);
+	lua_pushnumber(L, ((p.packed16 >> 30) & 0x3)   / (double)0x3);
+	return 4;
+}
+
 typedef void(*checkpixel)(lua_State *L, int startidx, Pixel &p);
 typedef int(*pushpixel)(lua_State *L, const Pixel &p);
 
@@ -504,6 +583,10 @@ extern "C" int luaopen_imagedata(lua_State *L)
 	checkFormats[PIXELFORMAT_R32F]    = luax_checkpixel_r32f;
 	checkFormats[PIXELFORMAT_RG32F]   = luax_checkpixel_rg32f;
 	checkFormats[PIXELFORMAT_RGBA32F] = luax_checkpixel_rgba32f;
+	checkFormats[PIXELFORMAT_RGBA4]   = luax_checkpixel_rgba4;
+	checkFormats[PIXELFORMAT_RGB5A1]  = luax_checkpixel_rgb5a1;
+	checkFormats[PIXELFORMAT_RGB565]  = luax_checkpixel_rgb565;
+	checkFormats[PIXELFORMAT_RGB10A2] = luax_checkpixel_rgb10a2;
 
 	pushFormats[PIXELFORMAT_R8]      = luax_pushpixel_r8;
 	pushFormats[PIXELFORMAT_RG8]     = luax_pushpixel_rg8;
@@ -517,6 +600,10 @@ extern "C" int luaopen_imagedata(lua_State *L)
 	pushFormats[PIXELFORMAT_R32F]    = luax_pushpixel_r32f;
 	pushFormats[PIXELFORMAT_RG32F]   = luax_pushpixel_rg32f;
 	pushFormats[PIXELFORMAT_RGBA32F] = luax_pushpixel_rgba32f;
+	pushFormats[PIXELFORMAT_RGBA4]   = luax_pushpixel_rgba4;
+	pushFormats[PIXELFORMAT_RGB5A1]  = luax_pushpixel_rgb5a1;
+	pushFormats[PIXELFORMAT_RGB565]  = luax_pushpixel_rgb565;
+	pushFormats[PIXELFORMAT_RGB10A2] = luax_pushpixel_rgb10a2;
 
 	int ret = luax_register_type(L, &ImageData::type, data::w_Data_functions, w_ImageData_functions, nullptr);
 

+ 100 - 14
src/modules/image/wrap_ImageData.lua

@@ -73,6 +73,9 @@ end
 local status, ffi = pcall(require, "ffi")
 if not status then return end
 
+local bitstatus, bit = pcall(require, "bit")
+if not bitstatus then return end
+
 pcall(ffi.cdef, [[
 typedef struct Proxy Proxy;
 typedef uint16_t half;
@@ -101,6 +104,11 @@ struct ImageData_Pixel_RGBA16F { half r, g, b, a; };
 struct ImageData_Pixel_R32F { float r; };
 struct ImageData_Pixel_RG32F { float r, g; };
 struct ImageData_Pixel_RGBA32F { float r, g, b, a; };
+
+struct ImageData_Pixel_RGBA4 { uint16_t rgba; };
+struct ImageData_Pixel_RGB5A1 { uint16_t rgba; };
+struct ImageData_Pixel_RGB565 { uint16_t rgb; };
+struct ImageData_Pixel_RGB10A2 { uint32_t rgba; };
 ]])
 
 local ffifuncs = ffi.cast("FFI_ImageData **", ffifuncspointer_str)[0]
@@ -112,7 +120,7 @@ local conversions = {
 			return tonumber(self.r) / 255, 0, 0, 1
 		end,
 		fromlua = function(self, r)
-			self.r = clamp01(r) * 255
+			self.r = (clamp01(r) * 255) + 0.5
 		end,
 	},
 	rg8 = {
@@ -121,8 +129,8 @@ local conversions = {
 			return tonumber(self.r) / 255, tonumber(self.g) / 255, 0, 1
 		end,
 		fromlua = function(self, r, g)
-			self.r = clamp01(r) * 255
-			self.g = clamp01(g) * 255
+			self.r = (clamp01(r) * 255) + 0.5
+			self.g = (clamp01(g) * 255) + 0.5
 		end,
 	},
 	rgba8 = {
@@ -131,10 +139,10 @@ local conversions = {
 			return tonumber(self.r) / 255, tonumber(self.g) / 255, tonumber(self.b) / 255, tonumber(self.a) / 255
 		end,
 		fromlua = function(self, r, g, b, a)
-			self.r = clamp01(r) * 255
-			self.g = clamp01(g) * 255
-			self.b = clamp01(b) * 255
-			self.a = a == nil and 255 or clamp01(a) * 255
+			self.r = (clamp01(r) * 255) + 0.5
+			self.g = (clamp01(g) * 255) + 0.5
+			self.b = (clamp01(b) * 255) + 0.5
+			self.a = a == nil and 255 or (clamp01(a) * 255) + 0.5
 		end,
 	},
 	r16 = {
@@ -143,7 +151,7 @@ local conversions = {
 			return tonumber(self.r) / 65535, 0, 0, 1
 		end,
 		fromlua = function(self, r)
-			self.r = clamp01(r) * 65535
+			self.r = (clamp01(r) * 65535) + 0.5
 		end,
 	},
 	rg16 = {
@@ -152,8 +160,8 @@ local conversions = {
 			return tonumber(self.r) / 65535, tonumber(self.g) / 65535, 0, 1
 		end,
 		fromlua = function(self, r, g)
-			self.r = clamp01(r) * 65535
-			self.g = clamp01(g) * 65535
+			self.r = (clamp01(r) * 65535) + 0.5
+			self.g = (clamp01(g) * 65535) + 0.5
 		end,
 	},
 	rgba16 = {
@@ -162,10 +170,10 @@ local conversions = {
 			return tonumber(self.r) / 65535, tonumber(self.g) / 65535, tonumber(self.b) / 65535, tonumber(self.a) / 65535
 		end,
 		fromlua = function(self, r, g, b, a)
-			self.r = clamp01(r) * 65535
-			self.g = clamp01(g) * 65535
-			self.b = clamp01(b) * 65535
-			self.a = a == nil and 65535 or clamp01(a) * 65535
+			self.r = (clamp01(r) * 65535) + 0.5
+			self.g = (clamp01(g) * 65535) + 0.5
+			self.b = (clamp01(b) * 65535) + 0.5
+			self.a = a == nil and 65535 or (clamp01(a) * 65535) + 0.5
 		end,
 	},
 	r16f = {
@@ -233,6 +241,84 @@ local conversions = {
 			self.a = a == nil and 1.0 or a
 		end,
 	},
+	rgba4 = {
+		-- LSB->MSB: [a, b, g, r]
+		pointer = ffi.typeof("struct ImageData_Pixel_RGBA4 *"),
+		tolua = function(self)
+			local rgba = self.rgba
+			local a = tonumber(bit.band(rgba, 0xF)) / 0xF
+			local b = tonumber(bit.band(bit.rshift(rgba, 4), 0xF)) / 0xF
+			local g = tonumber(bit.band(bit.rshift(rgba, 8), 0xF)) / 0xF
+			local r = tonumber(bit.rshift(rgba, 12)) / 0xF
+			return r, g, b, a
+		end,
+		fromlua = function(self, r, g, b, a)
+			-- bit functions round internally.
+			r = clamp01(r) * 0xF
+			g = clamp01(g) * 0xF
+			b = clamp01(b) * 0xF
+			a = a == nil and 0xF or clamp01(a) * 0xF
+			self.rgba = bit.bor(a, bit.lshift(b, 4), bit.lshift(g, 8), bit.lshift(r, 12))
+		end,
+	},
+	rgb5a1 = {
+		-- LSB->MSB: [a, b, g, r]
+		pointer = ffi.typeof("struct ImageData_Pixel_RGB5A1 *"),
+		tolua = function(self)
+			local rgba = self.rgba
+			local r = tonumber(bit.band(bit.rshift(rgba, 11), 0x1F)) / 0x1F
+			local g = tonumber(bit.band(bit.rshift(rgba,  6), 0x1F)) / 0x1F
+			local b = tonumber(bit.band(bit.rshift(rgba,  1), 0x1F)) / 0x1F
+			local a = tonumber(bit.band(rgba, 0x1))
+			return r, g, b, a
+		end,
+		fromlua = function(self, r, g, b, a)
+			-- bit functions round internally.
+			r = clamp01(r) * 0x1F
+			g = clamp01(g) * 0x1F
+			b = clamp01(b) * 0x1F
+			a = a == nil and 1 or clamp01(a)
+			self.rgba = bit.bor(bit.lshift(r, 11), bit.lshift(g, 6), bit.lshift(b, 1), a)
+		end,
+	},
+	rgb565 = {
+		-- LSB->MSB: [b, g, r]
+		pointer = ffi.typeof("struct ImageData_Pixel_RGB565 *"),
+		tolua = function(self)
+			local rgb = self.rgb
+			local r = bit.band(bit.rshift(rgb, 11), 0x1F) / 0x1F
+			local g = bit.band(bit.rshift(rgb, 5), 0x3F) / 0x3F
+			local b = bit.band(rgb, 0x1F) / 0x1F
+			return r, g, b, 1
+		end,
+		fromlua = function(self, r, g, b)
+			-- bit functions round internally.
+			r = clamp01(r) * 0x1F
+			g = clamp01(g) * 0x3F
+			b = clamp01(b) * 0x1F
+			self.rgb = bit.bor(bit.lshift(r, 11), bit.lshift(g, 5), b)
+		end,
+	},
+	rgb10a2 = {
+		-- LSB->MSB: [r, g, b, a]
+		pointer = ffi.typeof("struct ImageData_Pixel_RGB10A2 *"),
+		tolua = function(self)
+			local rgba = self.rgba
+			local r = tonumber(bit.band(rgba, 0x3FF)) / 0x3FF
+			local g = tonumber(bit.band(bit.rshift(rgba, 10), 0x3FF)) / 0x3FF
+			local b = tonumber(bit.band(bit.rshift(rgba, 20), 0x3FF)) / 0x3FF
+			local a = tonumber(bit.rshift(rgba, 30)) / 0x3
+			return r, g, b, a
+		end,
+		fromlua = function(self, r, g, b, a)
+			-- bit functions round internally.
+			r = clamp01(r) * 0x3FF
+			g = clamp01(g) * 0x3FF
+			b = clamp01(b) * 0x3FF
+			a = a == nil and 0x3 or clamp01(a) * 0x3
+			self.rgba = bit.bor(r, bit.lshift(g, 10), bit.lshift(b, 20), bit.lshift(a, 30))
+		end,
+	},
 }
 
 local _getWidth = ImageData.getWidth