diff --git a/init.lua b/init.lua index 1a0bd47..52918be 100644 --- a/init.lua +++ b/init.lua @@ -1,5 +1,23 @@ tga_encoder = {} +tga_encoder.features = {} +tga_encoder.features.color_format = { + ["A1R5G5B5"] = true, + ["B8G8R8"] = true, + ["B8G8R8A8"] = true, + ["Y8"] = true, +} +tga_encoder.features.colormap = { +} +tga_encoder.features.compression = { + ["RAW"] = true, + ["RLE"] = true, +} +tga_encoder.features.scanline_order = { + ["bottom_top"] = true, + ["top_bottom"] = true, +} + local image = setmetatable({}, { __call = function(self, ...) local t = setmetatable({}, {__index = self}) @@ -58,8 +76,8 @@ function image:encode_image_spec(properties) ) local scanline_order = properties.scanline_order assert ( - "bottom-top" == scanline_order or - "top-bottom" == scanline_order + "bottom_top" == scanline_order or + "top_bottom" == scanline_order ) local pixel_depth if 0 ~= #properties.colormap then @@ -73,16 +91,21 @@ function image:encode_image_spec(properties) local x_origin_hi = 0 local y_origin_lo = 0 local y_origin_hi = 0 - local image_descriptor = 0 -- equal to bottom-top scanline order + local image_descriptor = 0 -- equal to bottom_top scanline order local width_lo = self.width % 256 local width_hi = math.floor(self.width / 256) local height_lo = self.height % 256 local height_hi = math.floor(self.height / 256) - if "top-bottom" == scanline_order then + if "top_bottom" == scanline_order then image_descriptor = 32 y_origin_lo = height_lo y_origin_hi = height_hi end + if "A1R5G5B5" == color_format then + image_descriptor = image_descriptor + 1 -- 1 bit alpha channel + elseif "B8G8R8A8" == color_format then + image_descriptor = image_descriptor + 8 -- 8 bit alpha channel + end self.data = self.data .. string.char ( x_origin_lo, x_origin_hi, y_origin_lo, y_origin_hi, @@ -112,7 +135,8 @@ function image:encode_colormap(properties) local max_sample_out = math.pow(2, 5) - 1 for i = 1,#colormap,1 do local color = colormap[i] - local colorword = 32768 + + local colorword = 0 + + ((math.floor((color[4] / max_sample_in) + 0.5)) * 32768) + -- transparent if alpha < 128 ((math.floor((color[1] * max_sample_out / max_sample_in) + 0.5)) * 1024) + ((math.floor((color[2] * max_sample_out / max_sample_in) + 0.5)) * 32) + ((math.floor((color[3] * max_sample_out / max_sample_in) + 0.5)) * 1) @@ -207,9 +231,17 @@ function image:encode_data(properties) end else if "RAW" == compression then - self:encode_data_R8G8B8_as_A1R5G5B5_raw() + if 24 == self.pixel_depth then + self:encode_data_R8G8B8_as_A1R5G5B5_raw() + elseif 32 == self.pixel_depth then + self:encode_data_R8G8B8A8_as_A1R5G5B5_raw() + end elseif "RLE" == compression then - self:encode_data_R8G8B8_as_A1R5G5B5_rle() + if 24 == self.pixel_depth then + self:encode_data_R8G8B8_as_A1R5G5B5_rle() + elseif 32 == self.pixel_depth then + self:encode_data_R8G8B8A8_as_A1R5G5B5_rle() + end end end elseif "B8G8R8" == color_format then @@ -284,6 +316,23 @@ end function image:encode_data_R8G8B8_as_A1R5G5B5_raw() assert(24 == self.pixel_depth) + for _, row in ipairs(self.pixels) do + for _, pixel in ipairs(row) do + pixel[4] = 255 + end + end + self.pixel_depth = 32 + self:encode_data_R8G8B8A8_as_A1R5G5B5_raw() + for _, row in ipairs(self.pixels) do + for _, pixel in ipairs(row) do + pixel[4] = nil + end + end + self.pixel_depth = 24 +end + +function image:encode_data_R8G8B8A8_as_A1R5G5B5_raw() + assert(32 == self.pixel_depth) local raw_pixels = {} -- Sample depth rescaling is done according to the algorithm presented in: -- @@ -291,7 +340,8 @@ function image:encode_data_R8G8B8_as_A1R5G5B5_raw() local max_sample_out = math.pow(2, 5) - 1 for _, row in ipairs(self.pixels) do for _, pixel in ipairs(row) do - local colorword = 32768 + + local colorword = 0 + + ((math.floor((pixel[4] / max_sample_in) + 0.5)) * 32768) + -- transparent if alpha < 128 ((math.floor((pixel[1] * max_sample_out / max_sample_in) + 0.5)) * 1024) + ((math.floor((pixel[2] * max_sample_out / max_sample_in) + 0.5)) * 32) + ((math.floor((pixel[3] * max_sample_out / max_sample_in) + 0.5)) * 1) @@ -304,10 +354,28 @@ end function image:encode_data_R8G8B8_as_A1R5G5B5_rle() assert(24 == self.pixel_depth) + for _, row in ipairs(self.pixels) do + for _, pixel in ipairs(row) do + pixel[4] = 255 + end + end + self.pixel_depth = 32 + self:encode_data_R8G8B8A8_as_A1R5G5B5_rle() + for _, row in ipairs(self.pixels) do + for _, pixel in ipairs(row) do + pixel[4] = nil + end + end + self.pixel_depth = 24 +end + +function image:encode_data_R8G8B8A8_as_A1R5G5B5_rle() + assert(32 == self.pixel_depth) local colorword = nil local previous_r = nil local previous_g = nil local previous_b = nil + local previous_a = nil local raw_pixel = '' local raw_pixels = {} local count = 1 @@ -320,9 +388,10 @@ function image:encode_data_R8G8B8_as_A1R5G5B5_rle() local max_sample_out = math.pow(2, 5) - 1 for _, row in ipairs(self.pixels) do for _, pixel in ipairs(row) do - if pixel[1] ~= previous_r or pixel[2] ~= previous_g or pixel[3] ~= previous_b or count == 128 then + if pixel[1] ~= previous_r or pixel[2] ~= previous_g or pixel[3] ~= previous_b or pixel[4] ~= previous_a or count == 128 then if nil ~= previous_r then - colorword = 32768 + + colorword = 0 + + ((math.floor((previous_a / max_sample_in) + 0.5)) * 32768) + -- transparent if alpha < 128 ((math.floor((previous_r * max_sample_out / max_sample_in) + 0.5)) * 1024) + ((math.floor((previous_g * max_sample_out / max_sample_in) + 0.5)) * 32) + ((math.floor((previous_b * max_sample_out / max_sample_in) + 0.5)) * 1) @@ -357,12 +426,14 @@ function image:encode_data_R8G8B8_as_A1R5G5B5_rle() previous_r = pixel[1] previous_g = pixel[2] previous_b = pixel[3] + previous_a = pixel[4] else count = count + 1 end end end - colorword = 32768 + + colorword = 0 + + ((math.floor((previous_a / max_sample_in) + 0.5)) * 32768) + -- transparent if alpha < 128 ((math.floor((previous_r * max_sample_out / max_sample_in) + 0.5)) * 1024) + ((math.floor((previous_g * max_sample_out / max_sample_in) + 0.5)) * 32) + ((math.floor((previous_b * max_sample_out / max_sample_in) + 0.5)) * 1) @@ -585,7 +656,7 @@ function image:encode(properties) local properties = properties or {} properties.colormap = properties.colormap or {} properties.compression = properties.compression or "RAW" - properties.scanline_order = properties.scanline_order or "bottom-top" + properties.scanline_order = properties.scanline_order or "bottom_top" self.pixel_depth = #self.pixels[1][1] * 8 diff --git a/tools/generate_test_textures.lua b/tools/generate_test_textures.lua index 24e4b32..b3e2765 100755 --- a/tools/generate_test_textures.lua +++ b/tools/generate_test_textures.lua @@ -5,10 +5,11 @@ local _ = { 0 } local R = { 1 } local G = { 2 } local B = { 3 } +local W = { 4 } local pixels_colormapped_bt = { - { _, _, _, _, _, B, _, B, }, - { _, _, _, _, _, B, B, B, }, + { W, W, _, _, _, B, _, B, }, + { W, W, _, _, _, B, B, B, }, { _, _, G, G, G, B, _, B, }, { _, _, G, _, G, B, B, B, }, { _, R, G, _, _, _, _, _, }, @@ -24,59 +25,85 @@ local pixels_colormapped_tb = { { _, R, G, _, _, _, _, _, }, { _, _, G, _, G, B, B, B, }, { _, _, G, G, G, B, _, B, }, - { _, _, _, _, _, B, B, B, }, - { _, _, _, _, _, B, _, B, }, + { W, W, _, _, _, B, B, B, }, + { W, W, _, _, _, B, _, B, }, } -image_colormapped_bt = tga_encoder.image(pixels_colormapped_bt) -image_colormapped_tb = tga_encoder.image(pixels_colormapped_tb) +local pixels_colormapped_by_scanline_order = { + ["bottom_top"] = pixels_colormapped_bt, + ["top_bottom"] = pixels_colormapped_tb, +} -colormap_32bpp = { - { 0, 0, 0, 128 }, +local colormap_32bpp = { + { 0, 0, 0, 127 }, { 255, 0, 0, 255 }, { 0, 255, 0, 255 }, { 0, 0, 255, 255 }, + { 255, 255, 255, 128 }, } -image_colormapped_bt:save( - "type1_32bpp_bt.tga", - { colormap = colormap_32bpp, color_format = "B8G8R8A8", scanline_order = "bottom-top" } -) -image_colormapped_tb:save( - "type1_32bpp_tb.tga", - { colormap = colormap_32bpp, color_format = "B8G8R8A8", scanline_order = "top-bottom" } -) -image_colormapped_bt:save( - "type1_16bpp_bt.tga", - { colormap = colormap_32bpp, color_format = "A1R5G5B5", scanline_order = "bottom-top" } -) -image_colormapped_tb:save( - "type1_16bpp_tb.tga", - { colormap = colormap_32bpp, color_format = "A1R5G5B5", scanline_order = "top-bottom" } -) -colormap_24bpp = { +local colormap_24bpp = { { 0, 0, 0 }, { 255, 0, 0 }, { 0, 255, 0 }, { 0, 0, 255 }, + { 255, 255, 255 }, } -image_colormapped_bt:save( - "type1_24bpp_bt.tga", - { colormap = colormap_32bpp, color_format = "B8G8R8", scanline_order = "bottom-top" } -) -image_colormapped_tb:save( - "type1_24bpp_tb.tga", - { colormap = colormap_32bpp, color_format = "B8G8R8", scanline_order = "top-bottom" } -) -local _ = { 0, 0, 0, 128 } +local colormap_16bpp = { + { 0, 0, 0, 0 }, + { 255, 0, 0, 255 }, + { 0, 255, 0, 255 }, + { 0, 0, 255, 255 }, + { 255, 255, 255, 255 }, +} + +local colormap_by_color_format = { + ["A1R5G5B5"] = colormap_16bpp, + ["B8G8R8"] = colormap_24bpp, + ["B8G8R8A8"] = colormap_32bpp, +} + +for color_format, _ in pairs( + tga_encoder.features.color_format +) do + if ("Y8" ~= color_format) then + for scanline_order, _ in pairs( + tga_encoder.features.scanline_order + ) do + local filename + local pixels + filename = "type1" .. + '-' .. color_format .. + '-' .. scanline_order .. + '.tga' + pixels = pixels_colormapped_by_scanline_order[ + scanline_order + ] + local colormap = colormap_by_color_format[ + color_format + ] + local properties = { + colormap = colormap, + color_format = color_format, + scanline_order = scanline_order, + } + print(filename) + local image = tga_encoder.image(pixels) + image:save(filename, properties) + end + end +end + +local _ = { 0, 0, 0, 127 } local R = { 255, 0, 0, 255 } local G = { 0, 255, 0, 255 } local B = { 0, 0, 255, 255 } +local W = { 255, 255, 255, 128 } local pixels_rgba_bt = { - { _, _, _, _, _, B, _, B, }, - { _, _, _, _, _, B, B, B, }, + { W, W, _, _, _, B, _, B, }, + { W, W, _, _, _, B, B, B, }, { _, _, G, G, G, B, _, B, }, { _, _, G, _, G, B, B, B, }, { _, R, G, _, _, _, _, _, }, @@ -92,38 +119,24 @@ local pixels_rgba_tb = { { _, R, G, _, _, _, _, _, }, { _, _, G, _, G, B, B, B, }, { _, _, G, G, G, B, _, B, }, - { _, _, _, _, _, B, B, B, }, - { _, _, _, _, _, B, _, B, }, + { W, W, _, _, _, B, B, B, }, + { W, W, _, _, _, B, _, B, }, } -image_rgba_bt = tga_encoder.image(pixels_rgba_bt) -image_rgba_tb = tga_encoder.image(pixels_rgba_tb) - -image_rgba_bt:save( - "type2_32bpp_bt.tga", - { color_format="B8G8R8A8", compression="RAW", scanline_order = "bottom-top" } -) -image_rgba_tb:save( - "type2_32bpp_tb.tga", - { color_format="B8G8R8A8", compression="RAW", scanline_order = "top-bottom" } -) -image_rgba_bt:save( - "type10_32bpp_bt.tga", - { color_format="B8G8R8A8", compression="RLE", scanline_order = "bottom-top" } -) -image_rgba_tb:save( - "type10_32bpp_tb.tga", - { color_format="B8G8R8A8", compression="RLE", scanline_order = "top-bottom" } -) +local pixels_rgba_by_scanline_order = { + ["bottom_top"] = pixels_rgba_bt, + ["top_bottom"] = pixels_rgba_tb, +} local _ = { 0, 0, 0 } local R = { 255, 0, 0 } local G = { 0, 255, 0 } local B = { 0, 0, 255 } +local W = { 255, 255, 255 } local pixels_rgb_bt = { - { _, _, _, _, _, B, _, B, }, - { _, _, _, _, _, B, B, B, }, + { W, W, _, _, _, B, _, B, }, + { W, W, _, _, _, B, B, B, }, { _, _, G, G, G, B, _, B, }, { _, _, G, _, G, B, B, B, }, { _, R, G, _, _, _, _, _, }, @@ -139,42 +152,71 @@ local pixels_rgb_tb = { { _, R, G, _, _, _, _, _, }, { _, _, G, _, G, B, B, B, }, { _, _, G, G, G, B, _, B, }, - { _, _, _, _, _, B, B, B, }, - { _, _, _, _, _, B, _, B, }, + { W, W, _, _, _, B, B, B, }, + { W, W, _, _, _, B, _, B, }, } -image_rgb_bt = tga_encoder.image(pixels_rgb_bt) -image_rgb_tb = tga_encoder.image(pixels_rgb_tb) +local pixels_rgb_by_scanline_order = { + ["bottom_top"] = pixels_rgb_bt, + ["top_bottom"] = pixels_rgb_tb, +} -image_rgb_bt:save( - "type2_24bpp_bt.tga", - { color_format="B8G8R8", compression="RAW", scanline_order = "bottom-top" } -) -image_rgb_tb:save( - "type2_24bpp_tb.tga", - { color_format="B8G8R8", compression="RAW", scanline_order = "top-bottom" } -) -image_rgb_bt:save( - "type10_24bpp_bt.tga", - { color_format="B8G8R8", compression="RLE", scanline_order = "bottom-top" } -) -image_rgb_tb:save( - "type10_24bpp_tb.tga", - { color_format="B8G8R8", compression="RLE", scanline_order = "top-bottom" } -) -image_rgb_bt:save( - "type2_16bpp_bt.tga", - { color_format="A1R5G5B5", compression="RAW", scanline_order = "bottom-top" } -) -image_rgb_tb:save( - "type2_16bpp_tb.tga", - { color_format="A1R5G5B5", compression="RAW", scanline_order = "top-bottom" } -) -image_rgb_bt:save( - "type10_16bpp_bt.tga", - { color_format="A1R5G5B5", compression="RLE", scanline_order = "bottom-top" } -) -image_rgb_tb:save( - "type10_16bpp_tb.tga", - { color_format="A1R5G5B5", compression="RLE", scanline_order = "top-bottom" } -) +local tga_type_by_compression = { + ["RAW"] = "2", + ["RLE"] = "10", +} + +local pixels_by_scanline_order_by_color_format = { + ["A1R5G5B5"] = pixels_rgba_by_scanline_order, + ["B8G8R8"] = pixels_rgb_by_scanline_order, + ["B8G8R8A8"] = pixels_rgba_by_scanline_order, + ["Y8"] = pixels_rgb_by_scanline_order, +} + +for color_format, _ in pairs( + tga_encoder.features.color_format +) do + local pixels_by_scanline_order = pixels_by_scanline_order_by_color_format[ + color_format + ] + for compression, _ in pairs( + tga_encoder.features.compression + ) do + local tga_type + if ( + "Y8" == color_format and + "RAW" == compression + ) then + tga_type = "3" + else + tga_type = tga_type_by_compression[ + compression + ] + end + -- tga_encoder can not encode grayscale RLE (type 11) + if not( + "Y8" == color_format and + "RLE" == compression + ) then + for scanline_order, _ in pairs( + tga_encoder.features.scanline_order + ) do + local filename = "type" .. tga_type .. + '-' .. color_format .. + '-' .. scanline_order .. + '.tga' + local pixels = pixels_by_scanline_order[ + scanline_order + ] + local properties = { + color_format = color_format, + compression = compression, + scanline_order = scanline_order, + } + print(filename) + local image = tga_encoder.image(pixels) + image:save(filename, properties) + end + end + end +end