Add features table, refactor test texture generation, encode alpha for A1R5G5B5 images #1

Open
erlehmann wants to merge 15 commits from features-table into master
2 changed files with 220 additions and 107 deletions

View File

@ -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:
-- <https://www.w3.org/TR/2003/REC-PNG-20031110/#13Sample-depth-rescaling>
@ -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

View File

@ -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