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 = {}
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({}, { local image = setmetatable({}, {
__call = function(self, ...) __call = function(self, ...)
local t = setmetatable({}, {__index = self}) local t = setmetatable({}, {__index = self})
@ -58,8 +76,8 @@ function image:encode_image_spec(properties)
) )
local scanline_order = properties.scanline_order local scanline_order = properties.scanline_order
assert ( assert (
"bottom-top" == scanline_order or "bottom_top" == scanline_order or
"top-bottom" == scanline_order "top_bottom" == scanline_order
) )
local pixel_depth local pixel_depth
if 0 ~= #properties.colormap then if 0 ~= #properties.colormap then
@ -73,16 +91,21 @@ function image:encode_image_spec(properties)
local x_origin_hi = 0 local x_origin_hi = 0
local y_origin_lo = 0 local y_origin_lo = 0
local y_origin_hi = 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_lo = self.width % 256
local width_hi = math.floor(self.width / 256) local width_hi = math.floor(self.width / 256)
local height_lo = self.height % 256 local height_lo = self.height % 256
local height_hi = math.floor(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 image_descriptor = 32
y_origin_lo = height_lo y_origin_lo = height_lo
y_origin_hi = height_hi y_origin_hi = height_hi
end 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 ( self.data = self.data .. string.char (
x_origin_lo, x_origin_hi, x_origin_lo, x_origin_hi,
y_origin_lo, y_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 local max_sample_out = math.pow(2, 5) - 1
for i = 1,#colormap,1 do for i = 1,#colormap,1 do
local color = colormap[i] 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[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[2] * max_sample_out / max_sample_in) + 0.5)) * 32) +
((math.floor((color[3] * max_sample_out / max_sample_in) + 0.5)) * 1) ((math.floor((color[3] * max_sample_out / max_sample_in) + 0.5)) * 1)
@ -207,9 +231,17 @@ function image:encode_data(properties)
end end
else else
if "RAW" == compression then if "RAW" == compression then
if 24 == self.pixel_depth then
self:encode_data_R8G8B8_as_A1R5G5B5_raw() 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 elseif "RLE" == compression then
if 24 == self.pixel_depth then
self:encode_data_R8G8B8_as_A1R5G5B5_rle() self:encode_data_R8G8B8_as_A1R5G5B5_rle()
elseif 32 == self.pixel_depth then
self:encode_data_R8G8B8A8_as_A1R5G5B5_rle()
end
end end
end end
elseif "B8G8R8" == color_format then elseif "B8G8R8" == color_format then
@ -284,6 +316,23 @@ end
function image:encode_data_R8G8B8_as_A1R5G5B5_raw() function image:encode_data_R8G8B8_as_A1R5G5B5_raw()
assert(24 == self.pixel_depth) 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 = {} local raw_pixels = {}
-- Sample depth rescaling is done according to the algorithm presented in: -- Sample depth rescaling is done according to the algorithm presented in:
-- <https://www.w3.org/TR/2003/REC-PNG-20031110/#13Sample-depth-rescaling> -- <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 local max_sample_out = math.pow(2, 5) - 1
for _, row in ipairs(self.pixels) do for _, row in ipairs(self.pixels) do
for _, pixel in ipairs(row) 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[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[2] * max_sample_out / max_sample_in) + 0.5)) * 32) +
((math.floor((pixel[3] * max_sample_out / max_sample_in) + 0.5)) * 1) ((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() function image:encode_data_R8G8B8_as_A1R5G5B5_rle()
assert(24 == self.pixel_depth) 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 colorword = nil
local previous_r = nil local previous_r = nil
local previous_g = nil local previous_g = nil
local previous_b = nil local previous_b = nil
local previous_a = nil
local raw_pixel = '' local raw_pixel = ''
local raw_pixels = {} local raw_pixels = {}
local count = 1 local count = 1
@ -320,9 +388,10 @@ function image:encode_data_R8G8B8_as_A1R5G5B5_rle()
local max_sample_out = math.pow(2, 5) - 1 local max_sample_out = math.pow(2, 5) - 1
for _, row in ipairs(self.pixels) do for _, row in ipairs(self.pixels) do
for _, pixel in ipairs(row) 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 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_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_g * max_sample_out / max_sample_in) + 0.5)) * 32) +
((math.floor((previous_b * max_sample_out / max_sample_in) + 0.5)) * 1) ((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_r = pixel[1]
previous_g = pixel[2] previous_g = pixel[2]
previous_b = pixel[3] previous_b = pixel[3]
previous_a = pixel[4]
else else
count = count + 1 count = count + 1
end end
end 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_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_g * max_sample_out / max_sample_in) + 0.5)) * 32) +
((math.floor((previous_b * max_sample_out / max_sample_in) + 0.5)) * 1) ((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 {} local properties = properties or {}
properties.colormap = properties.colormap or {} properties.colormap = properties.colormap or {}
properties.compression = properties.compression or "RAW" 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 self.pixel_depth = #self.pixels[1][1] * 8

View File

@ -5,10 +5,11 @@ local _ = { 0 }
local R = { 1 } local R = { 1 }
local G = { 2 } local G = { 2 }
local B = { 3 } local B = { 3 }
local W = { 4 }
local pixels_colormapped_bt = { local pixels_colormapped_bt = {
{ _, _, _, _, _, B, _, B, }, { W, W, _, _, _, B, _, B, },
{ _, _, _, _, _, B, B, B, }, { W, W, _, _, _, B, B, B, },
{ _, _, G, G, G, B, _, B, }, { _, _, G, G, G, B, _, B, },
{ _, _, G, _, G, B, B, B, }, { _, _, G, _, G, B, B, B, },
{ _, R, G, _, _, _, _, _, }, { _, R, G, _, _, _, _, _, },
@ -24,59 +25,85 @@ local pixels_colormapped_tb = {
{ _, R, G, _, _, _, _, _, }, { _, R, G, _, _, _, _, _, },
{ _, _, G, _, G, B, B, B, }, { _, _, G, _, G, B, B, B, },
{ _, _, G, G, G, B, _, B, }, { _, _, G, G, G, B, _, B, },
{ _, _, _, _, _, B, B, B, }, { W, W, _, _, _, B, B, B, },
{ _, _, _, _, _, B, _, B, }, { W, W, _, _, _, B, _, B, },
} }
image_colormapped_bt = tga_encoder.image(pixels_colormapped_bt) local pixels_colormapped_by_scanline_order = {
image_colormapped_tb = tga_encoder.image(pixels_colormapped_tb) ["bottom_top"] = pixels_colormapped_bt,
["top_bottom"] = pixels_colormapped_tb,
}
colormap_32bpp = { local colormap_32bpp = {
{ 0, 0, 0, 128 }, { 0, 0, 0, 127 },
{ 255, 0, 0, 255 }, { 255, 0, 0, 255 },
{ 0, 255, 0, 255 }, { 0, 255, 0, 255 },
{ 0, 0, 255, 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 }, { 0, 0, 0 },
{ 255, 0, 0 }, { 255, 0, 0 },
{ 0, 255, 0 }, { 0, 255, 0 },
{ 0, 0, 255 }, { 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 R = { 255, 0, 0, 255 }
local G = { 0, 255, 0, 255 } local G = { 0, 255, 0, 255 }
local B = { 0, 0, 255, 255 } local B = { 0, 0, 255, 255 }
local W = { 255, 255, 255, 128 }
local pixels_rgba_bt = { local pixels_rgba_bt = {
{ _, _, _, _, _, B, _, B, }, { W, W, _, _, _, B, _, B, },
{ _, _, _, _, _, B, B, B, }, { W, W, _, _, _, B, B, B, },
{ _, _, G, G, G, B, _, B, }, { _, _, G, G, G, B, _, B, },
{ _, _, G, _, G, B, B, B, }, { _, _, G, _, G, B, B, B, },
{ _, R, G, _, _, _, _, _, }, { _, R, G, _, _, _, _, _, },
@ -92,38 +119,24 @@ local pixels_rgba_tb = {
{ _, R, G, _, _, _, _, _, }, { _, R, G, _, _, _, _, _, },
{ _, _, G, _, G, B, B, B, }, { _, _, G, _, G, B, B, B, },
{ _, _, G, G, G, B, _, B, }, { _, _, G, G, G, B, _, B, },
{ _, _, _, _, _, B, B, B, }, { W, W, _, _, _, B, B, B, },
{ _, _, _, _, _, B, _, B, }, { W, W, _, _, _, B, _, B, },
} }
image_rgba_bt = tga_encoder.image(pixels_rgba_bt) local pixels_rgba_by_scanline_order = {
image_rgba_tb = tga_encoder.image(pixels_rgba_tb) ["bottom_top"] = pixels_rgba_bt,
["top_bottom"] = 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 _ = { 0, 0, 0 } local _ = { 0, 0, 0 }
local R = { 255, 0, 0 } local R = { 255, 0, 0 }
local G = { 0, 255, 0 } local G = { 0, 255, 0 }
local B = { 0, 0, 255 } local B = { 0, 0, 255 }
local W = { 255, 255, 255 }
local pixels_rgb_bt = { local pixels_rgb_bt = {
{ _, _, _, _, _, B, _, B, }, { W, W, _, _, _, B, _, B, },
{ _, _, _, _, _, B, B, B, }, { W, W, _, _, _, B, B, B, },
{ _, _, G, G, G, B, _, B, }, { _, _, G, G, G, B, _, B, },
{ _, _, G, _, G, B, B, B, }, { _, _, G, _, G, B, B, B, },
{ _, R, G, _, _, _, _, _, }, { _, R, G, _, _, _, _, _, },
@ -139,42 +152,71 @@ local pixels_rgb_tb = {
{ _, R, G, _, _, _, _, _, }, { _, R, G, _, _, _, _, _, },
{ _, _, G, _, G, B, B, B, }, { _, _, G, _, G, B, B, B, },
{ _, _, G, G, G, B, _, B, }, { _, _, G, G, G, B, _, B, },
{ _, _, _, _, _, B, B, B, }, { W, W, _, _, _, B, B, B, },
{ _, _, _, _, _, B, _, B, }, { W, W, _, _, _, B, _, B, },
} }
image_rgb_bt = tga_encoder.image(pixels_rgb_bt) local pixels_rgb_by_scanline_order = {
image_rgb_tb = tga_encoder.image(pixels_rgb_tb) ["bottom_top"] = pixels_rgb_bt,
["top_bottom"] = pixels_rgb_tb,
}
image_rgb_bt:save( local tga_type_by_compression = {
"type2_24bpp_bt.tga", ["RAW"] = "2",
{ color_format="B8G8R8", compression="RAW", scanline_order = "bottom-top" } ["RLE"] = "10",
) }
image_rgb_tb:save(
"type2_24bpp_tb.tga", local pixels_by_scanline_order_by_color_format = {
{ color_format="B8G8R8", compression="RAW", scanline_order = "top-bottom" } ["A1R5G5B5"] = pixels_rgba_by_scanline_order,
) ["B8G8R8"] = pixels_rgb_by_scanline_order,
image_rgb_bt:save( ["B8G8R8A8"] = pixels_rgba_by_scanline_order,
"type10_24bpp_bt.tga", ["Y8"] = pixels_rgb_by_scanline_order,
{ color_format="B8G8R8", compression="RLE", scanline_order = "bottom-top" } }
)
image_rgb_tb:save( for color_format, _ in pairs(
"type10_24bpp_tb.tga", tga_encoder.features.color_format
{ color_format="B8G8R8", compression="RLE", scanline_order = "top-bottom" } ) do
) local pixels_by_scanline_order = pixels_by_scanline_order_by_color_format[
image_rgb_bt:save( color_format
"type2_16bpp_bt.tga", ]
{ color_format="A1R5G5B5", compression="RAW", scanline_order = "bottom-top" } for compression, _ in pairs(
) tga_encoder.features.compression
image_rgb_tb:save( ) do
"type2_16bpp_tb.tga", local tga_type
{ color_format="A1R5G5B5", compression="RAW", scanline_order = "top-bottom" } if (
) "Y8" == color_format and
image_rgb_bt:save( "RAW" == compression
"type10_16bpp_bt.tga", ) then
{ color_format="A1R5G5B5", compression="RLE", scanline_order = "bottom-top" } tga_type = "3"
) else
image_rgb_tb:save( tga_type = tga_type_by_compression[
"type10_16bpp_tb.tga", compression
{ color_format="A1R5G5B5", compression="RLE", scanline_order = "top-bottom" } ]
) 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